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); }