Универсальный медиаплеер
作者
using Base64
using FileIO
using WAV
function media_player(path::AbstractString; mode::String="audio", fs::Union{Nothing, Int}=nothing)
ext_map = Dict(
"audio" => ([".wav", ".mp3"], (f -> base64encode_audio(f, fs))),
"video" => ([".mp4"], base64encode_video),
"image" => ([".jpg", ".jpeg", ".png", ".gif", ".webp"], base64encode_image)
)
haskey(ext_map, mode) || error("Неподдерживаемый режим: $mode")
supported_extensions, encoder = ext_map[mode]
files = isdir(path) ? readdir(path, join=true) : [path]
selected_files = filter(f -> any(endswith(lowercase(f), ext) for ext in supported_extensions), files)
isempty(selected_files) && error("Файлы формата $mode не найдены по указанному пути.")
filenames = [basename(f) for f in selected_files]
encoded_data = [encoder(f) for f in selected_files]
base64_encoded = [data[1] for data in encoded_data]
mime_types = [data[2] for data in encoded_data]
unique_id = string(rand(UInt))
bg_color = "#f5f5f5"
text_color = "#555"
media_tag_name = mode == "audio" ? "audio" : mode == "video" ? "video" : "img"
controls_attr = mode in ["audio", "video"] ? "controls autoplay" : ""
media_html = if mode == "image"
"""<img id="$(media_tag_name)_$(unique_id)" src="data:$(mime_types[1]);base64,$(base64_encoded[1])"
style="max-width: 100%; border-radius: 10px;" />"""
else
"""
<$media_tag_name id="$(media_tag_name)_$(unique_id)" $controls_attr style="width: 100%; border-radius: 10px;">
<source id="src_$(unique_id)" src="data:$(mime_types[1]);base64,$(base64_encoded[1])" type="$(mime_types[1])" />
</$media_tag_name>
"""
end
html_interface = """
<div style="background: $bg_color; border-radius: 15px; padding: 15px; max-width: 640px; margin: 0 auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-size: 14px; color: $text_color;">
<button id="prevBtn_$(unique_id)" style="background: none; border: none; cursor: pointer; font-size: 22px;">⏮️</button>
<span id="name_$(unique_id)" style="font-weight: bold;">$(filenames[1]) (1 of $(length(filenames)))</span>
<button id="nextBtn_$(unique_id)" style="background: none; border: none; cursor: pointer; font-size: 22px;">⏭️</button>
$(mode == "audio" ? """<button id="loopToggle_$(unique_id)" style="background: none; border: none; cursor: pointer; font-size: 20px;" title="Toggle Loop">🔂</button>""" : "")
</div>
$media_html
</div>
<script>
let currentIndex_$(unique_id) = 0;
const mediaFiles_$(unique_id) = [$(join(["\"$(e)\"" for e in base64_encoded], ","))];
const mimeTypes_$(unique_id) = [$(join(["\"$(m)\"" for m in mime_types], ","))];
const fileNames_$(unique_id) = [$(join(["\"$(n)\"" for n in filenames], ","))];
let loopEnabled_$(unique_id) = false;
const mediaElement_$(unique_id) = document.getElementById("$(media_tag_name)_$(unique_id)");
const sourceElement_$(unique_id) = document.getElementById("src_$(unique_id)");
const nameElement_$(unique_id) = document.getElementById("name_$(unique_id)");
function updateMedia_$(unique_id)(newIndex) {
if (newIndex < 0) newIndex = mediaFiles_$(unique_id).length - 1;
if (newIndex >= mediaFiles_$(unique_id).length) newIndex = 0;
currentIndex_$(unique_id) = newIndex;
const currentMime = mimeTypes_$(unique_id)[newIndex];
const mediaData = "data:" + currentMime + ";base64," + mediaFiles_$(unique_id)[newIndex];
if ("$mode" === "image") {
mediaElement_$(unique_id).src = mediaData;
} else {
sourceElement_$(unique_id).src = mediaData;
sourceElement_$(unique_id).type = currentMime;
mediaElement_$(unique_id).load();
mediaElement_$(unique_id).play();
}
nameElement_$(unique_id).innerText = fileNames_$(unique_id)[newIndex] +
" (" + (newIndex + 1) + " of " + mediaFiles_$(unique_id).length + ")";
}
document.getElementById("prevBtn_$(unique_id)").onclick = function() {
updateMedia_$(unique_id)(currentIndex_$(unique_id) - 1);
};
document.getElementById("nextBtn_$(unique_id)").onclick = function() {
updateMedia_$(unique_id)(currentIndex_$(unique_id) + 1);
};
$(mode == "audio" ? """
const loopButton_$(unique_id) = document.getElementById("loopToggle_$(unique_id)");
loopButton_$(unique_id).onclick = function() {
loopEnabled_$(unique_id) = !loopEnabled_$(unique_id);
loopButton_$(unique_id).innerHTML = loopEnabled_$(unique_id) ? "🔁" : "🔂";
loopButton_$(unique_id).style.color = loopEnabled_$(unique_id) ? "dodgerblue" : "";
};
mediaElement_$(unique_id).addEventListener("ended", function() {
if (loopEnabled_$(unique_id)) {
updateMedia_$(unique_id)(currentIndex_$(unique_id) + 1);
}
});
""" : "")
</script>
"""
display("text/html", html_interface)
end
function base64encode_audio(filepath, fs)
ext = lowercase(splitext(filepath)[2])
if ext == ".wav"
audio_data, original_fs = wavread(filepath)
audio_data = ndims(audio_data) == 1 ? reshape(audio_data, :, 1) : audio_data
audio_data = clamp.(audio_data, -1, 1)
audio_int = round.(Int16, audio_data .* typemax(Int16))
buffer = IOBuffer()
wavwrite(audio_int, buffer; Fs=fs === nothing ? original_fs : fs, nbits=16)
seekstart(buffer)
return base64encode(take!(buffer)), "audio/wav"
elseif ext == ".mp3"
return base64encode(read(filepath)), "audio/mpeg"
else
error("Неподдерживаемый аудиоформат: $ext")
end
end
function base64encode_video(filepath)
lowercase(splitext(filepath)[2]) == ".mp4" || error("Поддерживается только MP4")
return base64encode(read(filepath)), "video/mp4"
end
function base64encode_image(filepath)
ext = lowercase(splitext(filepath)[2])
mime_type = if ext == ".jpg" || ext == ".jpeg"
"image/jpeg"
elseif ext == ".png"
"image/png"
elseif ext == ".gif"
"image/gif"
elseif ext == ".webp"
"image/webp"
else
error("Неподдерживаемый формат изображения: $ext")
end
return base64encode(read(filepath)), mime_type
end