I'm making a web video player which can change the video type from 2d to 3d, video quality from 2160p to 144p, video fps from 60fps to 5fps, audio from english to tamil and subtitles from off to other languages. I cannot make my video player work on IOS
because IOS
supports HLS
only. So I need guidance to how my make a HLS
video player which will operate EXACTLY like my DASH
like video player. I was able to make my DASH
like video player from this demo: http://nickdesaulniers.github.io/netfix/demo/bufferAll.html. So if there is a HLS
video player demo which shows something similar to this demo, I think I'll manage to make a HLS
video player. If HLS
cannot make my features, please state another methods. Thanks
Full sample project here: https://drive.google.com/file/d/156mDgIltBGMkXhx4LZfShxv3A8JrwkNP/view?usp=sharing
My Code (This is a small sample of my original video player):
<html>
<head>
<meta charset="utf-8"/>
<title>ORIGINAL</title>
</head>
<body>
<video controls style="width: 100%; height: 50%;"></video>
<br>
<select width="100px">
</select>
<br>
<div id="current_time_message" style="text-align: center; width: 100%; font-size: 25px;"></div>
<br>
Note: You can only make the chages using the 'select' before the Total Segements Loaded of this sample.
<script>
var set_timer = undefined;
var content_class = new Content_Class();
function Content_Class() {
var video_player = document.querySelector("video");
var total_segments = 30,
total_segements_call = false,
current_segment = 0;
var mediaSource = undefined,
sourceBuffer_video = undefined,
sourceBuffer_audio = undefined;
var current_video_type = "2d",
current_video_quality = "2160p",
current_video_frame_rate = "60fps",
current_audio = "english",
current_subtitle = "off",
changed_content = "";
var subtitles_list = [[undefined, "english", "English", "to_be_downloaded"], [undefined, "arabic", "Arabic", "to_be_downloaded"]];
var content_types_list = [["video-type_options", "2d", "3d"], ["video-quality_options", "2160p", "144p"], ["video-fps_options", "60fps", "5fps"], ["audio_options", "tamil", "english"], ["subtitle_options", "off", "english", "arabic"]];
var main_function_CALLS = "appending",
video_append_CALLS = undefined,
audio_append_CALLS = undefined,
seek_bar_time_change_CALLS = true,
re_appendable_check_CALLS = 0,
remove_for_call_change_part2_CALLS = 0,
new_content_CALLS = 0,
time_change_reappend_CALLS = 0;
function url_maker(type, number) {
var url = "resources/";
if (type == "video") {
return url += type + "/" + current_video_type + "/" + current_video_frame_rate + "/" + current_video_quality + "/" + current_video_type + "_" + current_video_frame_rate + "_" + current_video_quality + "_" + number + ".mp4";
} else if (type == "audio") {
return url += type + "/" + current_audio + "/" + current_audio + "_" + number + ".mp4";
} else {
return url += type + "/" + current_subtitle + "/" + current_subtitle + ".txt";
}
}
function start() {
mediaSource = new MediaSource();
video_player.src = URL.createObjectURL(mediaSource);
for (var x = 0; x < subtitles_list.length; x++)
{
subtitles_list[x][0] = video_player.addTextTrack("captions", undefined, subtitles_list[x][2]);
}
mediaSource.addEventListener('sourceopen', function () {
mediaSource.duration = total_segments * 5;
sourceBuffer_video = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.64000d"');
sourceBuffer_video.mode = 'sequence';
sourceBuffer_audio = mediaSource.addSourceBuffer('audio/mp4; codecs="mp4a.40.5"');
sourceBuffer_audio.mode = 'sequence';
Interactions();
main_function();
});
}
function Interactions() {
var current_time_message = document.getElementById("current_time_message");
var options = document.querySelector("select");
video_player.ontimeupdate = function () {
if (!total_segements_call) {
current_time_message.innerHTML = "Video Downloaded from " + new Date(video_player.currentTime * 1000).toISOString().substr(11, 8) + " to " + new Date(current_segment * 1000).toISOString().substr(11, 8);
}
}
for (var y=0;y<content_types_list.length;y++) {
for (var x=1;x<content_types_list[y].length;x++) {
var insert = document.createElement("option");
insert.text = content_types_list[y][0].split("_")[0].toUpperCase() + ": " + content_types_list[y][x].toUpperCase();
options.add(insert);
}
}
options.onchange = function() {
var text = options.options[options.selectedIndex].value;
text = text.split(": ");
changed_content = text[0].toLowerCase() + "_" + text[1].toLowerCase();
main_function_CALLS = "content changed";
}
}
function main_function() {
if (main_function_CALLS == "appending") {
console.log("----------", current_video_type, current_video_frame_rate, current_video_quality, current_audio, current_subtitle, "----------");
window.clearTimeout(set_timer);
current_segment++;
if (current_segment <= total_segments) {
appending_sources(current_segment);
} else {
var current_time_message = document.getElementById("current_time_message");
current_time_message.innerHTML = "Total segements Loaded";
total_segements_call = true;
}
} else if (main_function_CALLS == "content changed") {
change_content();
}
}
function appending_sources(x) {
video_append_CALLS = false;
audio_append_CALLS = false;
request_xhr (url_maker('video', x), 'arraybuffer', ['video', x, sourceBuffer_video], function (buffer) {
sourceBuffer_video.addEventListener('updateend', re_appendable_check);
sourceBuffer_video.appendBuffer(buffer);
video_append_CALLS = true;
});
request_xhr (url_maker('audio', x), 'arraybuffer', ['audio', x, sourceBuffer_audio], function (buffer) {
sourceBuffer_audio.addEventListener('updateend', re_appendable_check);
sourceBuffer_audio.appendBuffer(buffer);
audio_append_CALLS = true;
});
}
function re_appendable_check() {
if (++re_appendable_check_CALLS == 2) {
sourceBuffer_video.removeEventListener('updateend', re_appendable_check);
sourceBuffer_audio.removeEventListener('updateend', re_appendable_check);
re_appendable_check_CALLS = 0;
video_player_old_time = parseInt((video_player.currentTime / 5).toString().split(".")[0]);
main_function();
}
}
function crash_reappend(sourceBuffer, response, number, id) {
window.clearTimeout(set_timer);
try {
sourceBuffer.addEventListener('updateend', re_appendable_check);
sourceBuffer.appendBuffer(response);
console.log("FINALLY Appended " + id + ": " + number);
} catch (err) {
sourceBuffer.removeEventListener('updateend', re_appendable_check);
if (main_function_CALLS == "appending") {
console.log("Appending Again Failed of " + id + " at " + number);
set_timer = window.setTimeout(function(){crash_reappend(sourceBuffer, response, number, id);}, 5000);
} else {
remove_for_call_change(number);
}
}
}
function remove_for_call_change (number) {
window.clearTimeout(set_timer);
re_appendable_check_CALLS = 0;
function remove_for_call_change_part2() {
if (++remove_for_call_change_part2_CALLS == 2) {
sourceBuffer_video.removeEventListener('updateend', remove_for_call_change_part2);
sourceBuffer_audio.removeEventListener('updateend', remove_for_call_change_part2);
remove_for_call_change_part2_CALLS = 0;
current_segment -= 1;
sourceBuffer_video.timestampOffset = current_segment*5;
sourceBuffer_audio.timestampOffset = current_segment*5;
video_player_old_time = parseInt((video_player.currentTime / 5).toString().split(".")[0]);
main_function();
}
}
if (!sourceBuffer_audio.updating && !sourceBuffer_video.updating) {
re_appendable_check_CALLS = 0;
sourceBuffer_video.removeEventListener('updateend', re_appendable_check);
sourceBuffer_audio.removeEventListener('updateend', re_appendable_check);
sourceBuffer_video.addEventListener('updateend', remove_for_call_change_part2);
sourceBuffer_audio.addEventListener('updateend', remove_for_call_change_part2);
sourceBuffer_video.remove((number*5)-5, number*5);
sourceBuffer_audio.remove((number*5)-5, number*5);
} else {
set_timer = window.setTimeout(function(){remove_for_call_change(number);}, 2000);
}
}
function request_xhr(url, responsetype, type, input_function)
{
var xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.responseType = responsetype;
xhr.onload = function() {
try {
input_function(xhr.response);
console.log("Appended " + type[0] + ": " + type[1]);
} catch (err) {
if (responsetype != "") {
console.log("Appending Failed of " + type[0] + " at " + type[1]);
type[2].removeEventListener('updateend', re_appendable_check);
crash_reappend(type[2], xhr.response, type[1], type[0]);
} else {
console.log("SUBTITLE FAILED");
}
}
};
xhr.send();
}
function change_content() {
var change_content_type = changed_content.split("_")[0];
var change_content = changed_content.split("_")[1].toLowerCase();
if (change_content_type == "video-type") {
if (current_video_type != change_content) {
current_video_type = change_content;
new_content();
} else {main_function_CALLS = "appending";main_function();}
} else if (change_content_type == "video-quality") {
if (current_video_quality != change_content) {
current_video_quality = change_content;
new_content();
} else {main_function_CALLS = "appending";main_function();}
} else if (change_content_type == "video-fps") {
if (current_video_frame_rate != change_content) {
current_video_frame_rate = change_content;
new_content();
} else {main_function_CALLS = "appending";main_function();}
} else if (change_content_type == "audio") {
if (current_audio != change_content) {
current_audio = change_content;
new_content();
} else {main_function_CALLS = "appending";main_function();}
} else if (change_content_type == "subtitle") {
console.log(change_content_type, change_content);
if (current_subtitle != change_content) {
current_subtitle = change_content;
subtitle_change();
} else {main_function_CALLS = "appending";main_function();}
}
function new_content() {
function content_changed_reappend() {
if (++new_content_CALLS == 2) {
sourceBuffer_video.removeEventListener('updateend', content_changed_reappend);
sourceBuffer_audio.removeEventListener('updateend', content_changed_reappend);
new_content_CALLS = 0;
current_segment = parseInt((video_player.currentTime / 5).toString().split(".")[0]);
sourceBuffer_video.timestampOffset = current_segment*5;
sourceBuffer_audio.timestampOffset = current_segment*5;
main_function_CALLS = "appending";
main_function();
}
}
sourceBuffer_video.addEventListener('updateend', content_changed_reappend);
sourceBuffer_audio.addEventListener('updateend', content_changed_reappend);
sourceBuffer_video.remove(0, total_segments * 5);
sourceBuffer_audio.remove(0, total_segments * 5);
}
function subtitle_change() {
if (current_subtitle == "off") {
for (var x = 0; x < subtitles_list.length; x++) {
subtitles_list[x][0].mode = "hidden";
}
} else {
for (var x = 0; x < subtitles_list.length; x++) {
if (subtitles_list[x][1] == current_subtitle) {
if (subtitles_list[x][3] != "downloaded") {
var num = x; //wierd problem
subtitles_list[num][0].mode = "showing";
request_xhr(url_maker("subtitle", 0), "", ['subtitle', 0], function(buffer) {
var file = buffer.split('\n');
for (var y = 0; y < file.length; y+=2) {
subtitles_list[num][0].addCue(new VTTCue(file[y].split(" --> ")[0], file[y].split(" --> ")[1], file[y+1].replace(/\\n/g, '\n')));
}
});
subtitles_list[num][3] = "downloaded";
} else {
subtitles_list[x][0].mode = "showing";
}
} else {
subtitles_list[x][0].mode = "hidden";
}
}
}
main_function_CALLS = "appending";
main_function();
}
}
return {
start: start
};
}
set_timer = window.setTimeout(content_class.start(), 1);
</script>
</body>
</html>
In iOS, you can get HLS playback in a much simpler fashion, since it is natively supported by the <video>
tag. You can simply do <video src="https://example.com/manifest.m3u8">
and it will work.
Alternatively, you can try and use Media Source Extensions there as well, but writing your own player seems overly complicated. I would probably look at hls.js if I were you.