I've written a JavaScript
prototype which extends the video
tag with a new function.
myVideo.subtitle('/path/to/subtitle')
The HTML video
tag only supports WebVTT
as subtitles. So I thought, let's create support for .srt
files as well with a custom written script.
It doesn't add support throughout the <track>
element, however, I might add that in a future version. At the moment only adding a subtitle programmatically works.
You can even add a subtitle with a .txt
extension or whatever. As long as it's supported by the browser and the content of the file is formatted like below:
1
hour:minute:second,milliseconds --> hour:minute:second,milliseconds
this is the text
2
hour:minute:second,milliseconds --> hour:minute:second,milliseconds
this is more text
I'm planning to add more subtitle formats in future versions.
What can I do better and what can I do more efficiently? Thanks for taking your time, I really appreciate it.
Below is lots of code. Perhaps its easier to read / review here: https://codepen.io/richardmauritz/project/editor/DOzmNL#
window.addEventListener("DOMContentLoaded", function ()
{
var video = document.getElementById('video');
video.subtitle('/subs/sub.txt');
})
HTMLMediaElement.prototype.subtitle = function (file)
{
/**
* Without a file no need to execute script
*/
if (!file)
{
return;
}
var HTMLMediaElement = this;
/**
* Confirm element is a video
*/
if (HTMLMediaElement.tagName !== 'VIDEO')
{
return;
}
var subtitle = {
data: {
subtitles: [],
paragraphs: null,
element: null,
index: 0,
current: null,
next: null,
},
/**
* Sets innerHTML of the <sub> element
*/
setTextContent: function (text)
{
subtitle.data.element.innerHTML = text ? text : '';
},
/**
* Returns innerHTML of the <sub> element
*/
getTextContent: function ()
{
return subtitle.data.element.innerHTML;
},
/**
* Creates a subtitle element for the current video
*/
createElement: function ()
{
/**
* Check if subtitle element doensn't exists yet
*/
if (HTMLMediaElement.nextSibling.tagName !== 'undefined' && HTMLMediaElement.nextSibling.tagName !== 'SUB')
{
/**
* Insert nice subtitle font
*/
var font = document.createElement('link');
font.rel = 'stylesheet';
font.href = 'https://fonts.googleapis.com/css?family=Sunflower:300';
/**
* Append font
*/
document.head.appendChild(font);
/**
* Create new sub element
*/
var element = document.createElement('sub');
/**
* Store element into current subtitle object
*/
subtitle.data.element = element;
/**
* Append node to document
*/
HTMLMediaElement.parentNode.insertBefore(element, HTMLMediaElement.nextSibling);
}
},
/**
* Loads subtitle file over HTTP(S)
* Calls subtitle.parse(content)
*
* @param {string} - Path / URL to subtitle
*/
load: function (file)
{
/**
* XMLHttpRequest
*/
var request = new XMLHttpRequest();
request.open('GET', file);
request.onreadystatechange = function ()
{
/**
* Resolve promise, return subtitle contents
*/
if (request.responseText !== '')
{
subtitle.parse(request.responseText);
}
};
/**
* Send XMLHttpRequest
*/
request.send();
},
/**
* Parses subtitle file
*
* @param {string} - SRT text content
* @returns {object} - Object containing subtitles
*/
parse: function (content)
{
/**
* First split all paragraphs into chunks
*/
subtitle.data.paragraphs = content.split(/\n\s*\n/g);
for (var i = 0; i < subtitle.data.paragraphs.length; i++)
{
/**
* Temporary array
*/
var arr = subtitle.data.paragraphs[i].split('\n');
/**
* Store paragraph information
*/
subtitle.data.subtitles.push({
"$index": arr.slice(0, 1).join(),
"$timing": subtitle.stringToArray(arr.slice(1, 2).join().split(" --> ")),
"$textContent": arr.slice(2, arr.length).join()
});
};
/**
* Set defaults
*/
subtitle.data.current = subtitle.data.subtitles[subtitle.data.index];
subtitle.data.next = subtitle.data.subtitles[subtitle.data.index + 1];
subtitle.createElement();
},
/**
* Starts displaying the subtitles when video is started
* Gets called using the video.timeupdate event listener
*/
play: function ()
{
/**
* Set subtitle when video's currentTime matches the subtitle time
*/
if (subtitle.stringToArray(video.getCurrentTime.toString()).join('') > subtitle.data.current.$timing[0].join(''))
{
if (subtitle.getTextContent() === '')
{
subtitle.setTextContent(subtitle.data.current.$textContent);
}
};
/**
* Unset current and set next subtitle when video's currentTime is greater than subtitles end time
*/
if (subtitle.stringToArray(video.getCurrentTime.toString()).join('') > subtitle.data.current.$timing[1].join(''))
{
subtitle.setTextContent('');
subtitle.data.index++;
subtitle.data.current = subtitle.data.next;
subtitle.data.next = subtitle.data.subtitles[subtitle.data.index];
}
},
/**
* Converts SRT timing string (00:00:00,000) to array ['00', '00', '00', '000']
*
* @param {string} - SRT timing string Eg. 01:44:03,732 (hours:minutes:seconds:milliseconds)
* @returns {array} - Array ['hour', 'minute', 'seconds', 'milliseconds']
*/
stringToArray: function (string)
{
var response = [];
if (typeof string === 'object')
{
for (var i = 0; i < string.length; i++)
{
response.push(string[i].split(/[\:,]+/));
}
return response;
}
else
{
response.push(string.split(/[\:,]+/));
return response[0];
}
},
/**
* Gets the current active subtitle
*
* @returns {object} - Current subtitle
*/
getCurrentSubtitle: function ()
{
return subtitle.data.current;
},
getNextSubtitle: function ()
{
return subtitle.data.next;
},
setNextSubtitle: function ()
{
subtitle.data.index++;
subtitle.data.current = subtitle.data.next;
subtitle.data.next = subtitle.data.subtitles[subtitle.data.index];
},
/**
* Recalculates which subtitle is current and next
*/
recalculate: function ()
{
for (var i = 0; i < subtitle.data.subtitles.length; i++)
{
/**
* Find next subtitle based on video's currentTime
*/
if (subtitle.stringToArray(video.getCurrentTime.toString()).join('') < subtitle.data.subtitles[i].$timing[0].join(''))
{
/**
* Update subtitle data
*/
subtitle.data.index = i;
subtitle.data.current = subtitle.data.subtitles[i];
subtitle.data.next = subtitle.data.subtitles[i + 1];
/**
* Break for loop when matched
*/
break;
}
}
}
}
var video = {
/**
* Returns the current playback position in the video (in seconds)
*
* @returns {number} - Playback position in seconds
*/
getCurrentTime: {
/**
* Returns the video durantion hours
*
* @returns {string}
*/
hours: function ()
{
return Math.floor(HTMLMediaElement.currentTime / 3600) < 10 ? '0' + Math.floor(HTMLMediaElement.currentTime / 3600) : Math.floor(HTMLMediaElement.currentTime / 3600);
},
/**
* Returns the video durantion minutes
*
* @returns {string}
*/
minutes: function ()
{
return Math.floor(HTMLMediaElement.currentTime / 60) < 10 ? '0' + Math.floor(HTMLMediaElement.currentTime / 60) : Math.floor(HTMLMediaElement.currentTime / 60);
},
/**
* Returns the video durantion seconds
*
* @returns {string}
*/
seconds: function ()
{
return Math.floor(HTMLMediaElement.currentTime % 60) < 10 ? '0' + Math.floor(HTMLMediaElement.currentTime % 60) : Math.floor(HTMLMediaElement.currentTime % 60);
},
/**
* Returns the video durantion milliseconds
*
* @returns {string}
*/
milliseconds: function ()
{
return (HTMLMediaElement.currentTime % 60).toString().replace('.', '').substring(2, 5);
},
/**
* Returns the full duration in the same format as the subtitle
*
* @returns {string}
*/
toString: function ()
{
return video.getCurrentTime.hours() + ':' + video.getCurrentTime.minutes() + ':' + video.getCurrentTime.seconds() + ',' + video.getCurrentTime.milliseconds();
}
},
/**
* Fires when video starts playing or unpaused
*/
playing: function ()
{
subtitle.play();
},
/**
* Fires when video is set forwards or backwards
*/
seeking: function ()
{
subtitle.recalculate();
}
}
HTMLMediaElement.addEventListener('timeupdate', video.playing);
HTMLMediaElement.addEventListener('seeking', video.seeking);
/**
* Initialize the subtitle
*/
subtitle.load(file);
}