/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is HTML5 video itext demonstration code. * * The Initial Developer of the Original Code is Mozilla Corporation. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Silvia Pfeiffer * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // Stop JSLint whinging about globals // /*global jQuery: true, window: true, ITEXT_ERR: true, ItextCollection: true, Itext: true, LoadFile: true, categoryName: true, languageName: true, parseInt: true, parseSrt: true, parseLrc: true */ // Function to convert language code to language name // should be available inside the browser function languageName(abbrev) { // see http://www.iana.org/assignments/language-subtag-registry // no sign languages included right now var langHash = { "aa": "Afar", "ab": "Abkhazian", "ae": "Avestan", "af": "Africaans", "ak": "Akan", "am": "Amharic", "an": "Aragonese", "anp": "Angika", "ar": "Arabic", "ar-ae": "Arabic (U.A.E.)", "ar-bh": "Arabic (Bahrain)", "ar-dz": "Arabic (Algeria)", "ar-eg": "Arabic (Egypt)", "ar-iq": "Arabic (Iraq)", "ar-jo": "Arabic (Jordan)", "ar-kw": "Arabic (Kuwait)", "ar-lb": "Arabic (Lebanon)", "ar-ly": "Arabic (Libya)", "ar-ma": "Arabic (Morocco)", "ar-om": "Arabic (Oman)", "ar-qa": "Arabic (Qatar)", "ar-sa": "Arabic (Saudi Arabia)", "ar-sy": "Arabic (Syria)", "ar-tn": "Arabic (Tunisia)", "ar-ye": "Arabic (Yemen)", "as": "Assamese", "ast": "Asturian", "av": "Avaric", "ay": "Aymara", "az": "Azerbaijani", "ba": "Bashkir", "be": "Belarusian", "bg": "Bulgarian", "bg-bg": "Bulgarian (Bulgaria)", "bh": "Bihari", "bi": "Bislama", "bm": "Bambara", "bn": "Bengali", "bo": "Tibetan", "br": "Breton", "bs": "Bosnian", "ca": "Catalan", "ca-es": "Catalan (Catalan)", "ce": "Chechen", "ch": "Chamorro", "co": "Corsican", "cr": "Cree", "cs": "Czech", "cs-cz": "Czech (Czech Republic)", "cu": "Church Slavic", "cv": "Cuvash", "cy": "Welsh", "da": "Danish", "da-dk": "Danish (Denmark)", "de": "German", "de-at": "German (Austria)", "de-ch": "German (Swiss)", "de-de": "German (Germany)", "de-li": "Deutsch (Lichtenstein)", "de-lu": "Deutsch (Luxemburg)", "dv": "Divehi", "dz": "Dzongkha", "ee": "Ewe", "el": "Greek", "en": "English", "en-au": "English (Australia)", "en-bz": "English (Belize)", "en-ca": "English (Canada)", "en-gb": "English (Great Britan)", "en-ie": "English (Ireland)", "en-jm": "English (Jamaica)", "en-nz": "English (New Zealand)", "en-ph": "English (Philippines)", "en-uk": "English (Great Britan)", "en-us": "English (United States)", "en-tt": "English (Trinidad)", "en-za": "English (South Africa)", "en-zw": "English (Zimbabwe)", "eo": "Ensperanto", "es": "Spanish", "es-ar": "Spanish (Argentina)", "es-bo": "Spanish (Bolivia)", "es-cl": "Spanish (Chile)", "es-co": "Spanish (Colombia)", "es-cr": "Spanish (Costa Rica)", "es-do": "Spanish (Dominican Republic)", "es-ec": "Spanish (Ecuador)", "es-es": "Spanish (Spain)", "es-gt": "Spanish (Guatemala)", "es-hn": "Spanish (Honduras)", "es-sv": "Spanish (El Salvador)", "es-mx": "Spanish (Mexico)", "es-nt": "Spanish (Nicaragua)", "es-pa": "Spanish (Panama)", "es-pe": "Spanish (Peru)", "es-pr": "Spanish (Puerto Rico)", "es-py": "Spanish (Paraguay)", "es-uy": "Spanish (Uruguay)", "es-ve": "Spanish (Venezuela)", "et": "Estonian", "eu": "Basque", "fa": "Persian", "ff": "Fulah", "fi": "Finnish", "fj": "Fijian", "fo": "Faroese", "fr": "French", "fr-be": "French (Belgium)", "fr-ca": "French (Canada)", "fr-ch": "French (Swiss)", "fr-fr": "French (France)", "fr-lu": "French (Luxemburg)", "fr-mc": "French (Mexico)", "frr": "Frisian", "fy": "Western Frisian", "ga": "Irish", "gd": "Gaelic", "gl": "Galician", "gn": "Guarani", "gu": "Gujarati", "gv": "Manx", "ha": "Hausa", "he": "Hebrew", "hi": "Hindi", "ho": "Hiri Motu", "hr": "Croatian", "hsb": "High Sorbian", "ht": "Haitian", "hu": "Hungarian", "hy": "Armenian", "hz": "Herero", "ia": "Interlingua", "id": "Indonesian", "ie": "Interlingue", "ig": "Igbo", "ii": "Sichuan Yi", "ik": "Inupiaq", "in": "Indonesian", "io": "Ido", "is": "Icelandic", "it": "Italian", "it-ch": "Italian (Swiss)", "iu": "Inuktitut", "iw": "Hebrew", "ja": "Japanese", "ji": "Yiddish", "jv": "Javanese", "ka": "Georian", "kg": "Kongo", "ki": "Kikuyu", "kj": "Kuanyama", "kk": "Kasakh", "kl": "Kalaallisut", "km": "Central Khmer", "kn": "Kannada", "ko": "Korean", "ko-kp": "Korean (North Korea)", "ko-kr": "Korean (South Korea)", "kr": "Kanuri", "ks": "Kashmiri", "ku": "Kurdish", "kv": "Komi", "kw": "Cornish", "ky": "Kyrgyz", "la": "Latin", "lb": "Luxembourgish", "lg": "Ganda", "li": "Limburgan", "ln": "Lingala", "lo": "Lao", "lt": "Lithuanian", "lu": "Luba-Katanga", "lv": "Latvian", "mg": "Malagasy", "mh": "Marshallese", "mi": "Maori", "mk": "Macedonian", "mk-mk": "Macedonian (F.J.R. Macedonia)", "ml": "Malayalam", "mn": "Mongolian", "mo": "Moldavian", "mr": "Marathi", "ms": "Malay", "mt": "Maltese", "my": "Burmese", "na": "Nauru", "nb": "Nowegian Bokmål", "nd": "North Ndebele", "ne": "Nepali", "ng": "Ndonga", "nl": "Dutch", "nl-be": "Dutch (Belgium)", "nn": "Nowegian Nynorsk", "no": "Nowegian", "nr": "South Ndebele", "nv": "Navajo", "ny": "Chichewa", "oc": "Occitan", "oj": "Ojibwa", "om": "Oromo", "or": "Oriya", "os": "Ossetian", "pa": "Panjabi", "pi": "Pali", "pl": "Polish", "ps": "Pushto", "pt": "Portuguese", "pt-br": "Portuguese (Brasil)", "qu": "Quechua", "rm": "Romansh", "rn": "Rundi", "ro": "Romanian", "ru": "Russian", "rw": "Kinyarwanda", "sa": "Sanskit", "sc": "Sardinian", "sd": "Sindhi", "se": "Northern Sami", "sg": "Sango", "sh": "Serbo-Croatian", "si": "Sinhala", "sk": "Slovak", "sl": "Slovenian", "sm": "Samoan", "sn": "Shona", "so": "Somali", "sq": "Albanian", "sr": "Serbian", "ss": "Swati", "st": "Southern Sotho", "su": "Sundanese", "sv": "Swedish", "sv-fi": "Swedisch (Finnland)", "sw": "Swahili", "ta": "Tamil", "te": "Telugu", "tg": "Tajik", "th": "Thai", "ti": "Tigrinya", "tk": "Turkmen", "tl": "Tagalog", "tn": "Tswana", "to": "Tonga", "tr": "Turkish", "ts": "Tsonga", "tt": "Tatar", "tw": "Twi", "ty": "Tahitian", "ug": "Uighur", "uk": "Ukrainian", "ur": "Urdu", "uz": "Uzbek", "ve": "Venda", "vi": "Vietnamese", "vo": "Volapük", "wa": "Walloon", "wo": "Wolof", "xh": "Xhosa", "yi": "Yiddish", "yo": "Yoruba", "za": "Zhuang", "zh": "Chinese", "zh-chs": "Chinese (Simplified)", "zh-cht": "Chinese (Traditional)", "zh-cn": "Chinese (People's Republic of China)", "zh-guoyu": "Mandarin", "zh-hk": "Chinese (Hong Kong S.A.R.)", "zh-min-nan": "Min-Nan", "zh-mp": "Chinese (Macau S.A.R.)", "zh-sg": "Chinese (Singapore)", "zh-tw": "Chinese (Taiwan)", "zh-xiang": "Xiang", "zu": "Zulu" }; if (langHash[abbrev]) { return langHash[abbrev]; } else { return null; } } // Function to convert category code to category name function categoryName(abbrev) { // see http://www.iana.org/assignments/language-subtag-registry var catHash = { "CC": "Captions", "SUB": "Subtitles", "TAD": "Audio Description", "KTV": "Karaoke", "TIK": "Ticker Text", "AR": "Active Regions", "NB": "Annotation", "META": "Timed Metadata", "TRX": "Transcript", "LRC": "Lyrics", "LIN": "Linguistic Markup", "CUE": "Cue Points" }; if (catHash[abbrev]) { return catHash[abbrev]; } else { return null; } } // This is where the implementation of iText starts // list of potential errors created with iText parsing var ITEXT_ERR = { ABORTED: 1, // fetching aborted NETWORK: 2, // network error PARSE: 3, // parsing error of itext resource SRC_NOT_SUPPORTED: 4, // unsuitable itext resource LANG: 5 // language mismatch }; // class to load a file, call the right parsing function, // and keep the parsed text segments var LoadFile = function (url, charset, type) { this.load(url, charset, type); }; LoadFile.prototype = { url: null, textElements: [], error: 0, load: function (url, charset, type) { this.url = url; var handler = null; var content = []; var error = 0; // choose parsing function if (type === "text/srt") { handler = parseSrt; } else if (type === "text/lrc") { handler = parseLrc; } else { // no handler for given file type this.error = ITEXT_ERR.SRC_NOT_SUPPORTED; } // set the character encoding before the ajax request jQuery.ajaxSetup({ 'beforeSend' : function (xhr) { xhr.overrideMimeType("text/text; charset=" + charset); } }); jQuery.ajax({ type: "GET", url: url, data: {}, success: function (data, textStatus) { content = handler(data); }, error: function () { error = ITEXT_ERR.NETWORK; }, dataType: 'text', async: false, cache: false // REMOVE AFTER TESTING: FIXME }); if (!error && !content) { this.error = ITEXT_ERR.PARSE; } else if (error) { this.error = error; } this.textElements = content; } }; // class to hold an itext track var Itext = function (track) { this.init(track); }; Itext.prototype = { src: null, lang: null, langName: null, type: "text/srt", charset: null, delay: 0, stretch: 100, fetched: false, error: 0, allText: [], init: function (itext) { this.src = jQuery(itext).attr("src"); this.lang = jQuery(itext).attr("lang"); this.langName = languageName(this.lang); this.type = (jQuery(itext).attr("type") || "text/srt"); this.charset = (jQuery(itext).attr("charset") || "UTF-8"); this.delay = (jQuery(itext). attr("delay") || 0); this.stretch = (jQuery(itext).attr("stretch") || 100); }, fetch: function () { if (this.type === "text/srt" || this.type === "text/lrc") { var file = new LoadFile(this.src, this.charset, this.type); this.error = file.error; this.allText = file.textElements; // adjust for delay and stretch factor for (var i = 0; i < this.allText.length; i++) { this.allText[i].start = (this.allText[i].start * (this.stretch/100.0)) + this.delay; this.allText[i].end = (this.allText[i].end * (this.stretch/100.0)) + this.delay; } this.fetched = true; } else { this.error = ITEXT_ERR.SRC_NOT_SUPPORTED; } }, currentText: function (currentTime) { var lines = []; for (var i = 0; i < this.allText.length; i++) { if (this.allText[i].end) { if (currentTime >= this.allText[i].start && currentTime < this.allText[i].end) { lines.push('
' + this.allText[i].content + '
'); } } else { if (currentTime >= this.allText[i].start) { lines.push('
' + this.allText[i].content + '
'); } } } // produce output var content; if (lines.length === 0) { content = null; } else { content = lines.join("
\n"); } return content; } }; // class to hold an itextlist var ItextList = function (itextlist) { this.init(itextlist); }; ItextList.prototype = { itextlist: null, category: null, active: "auto", name: null, itexts: [], onenter: null, onleave: null, primary_lang: null, secondary_lang: null, init: function (itextlist) { this.itextlist = itextlist; this.category = jQuery(itextlist).attr("category"); this.active = (jQuery(itextlist).attr("active") || "none"); this.name = jQuery(itextlist).attr("name"); this.onenter = jQuery(itextlist).attr("onenter"); this.onleave = jQuery(itextlist).attr("onleave"); // parse the itext elements this.load(); // fetch the appropriate track var default_lang; if (this.primary_lang) { default_lang = this.primary_lang; } else if (this.secondary_lang) { default_lang = this.secondary_lang; } if (this.active != "none") { if (this.active == "auto") { this.active = default_lang; } else { if (!this.itexts[this.active]) { this.active = "none"; } } this.itexts[this.active].fetch(); } }, load: function() { var tracks_tmp = this.itextlist.find('itext'); var itexts_tmp = {}; var primary_lang_tmp = null; var secondary_lang_tmp = null; tracks_tmp.each(function (i) { // create the text track and add to the itexts array var track = new Itext(jQuery(this)); itexts_tmp[track.lang] = {}; itexts_tmp[track.lang] = track; // check for appropriate language in this track for later fetching if (track.lang === window.navigator.language) { primary_lang_tmp = track.lang; // complete lang match } else if (track.lang === window.navigator.language.substr(0, 2)) { secondary_lang_tmp = track.lang; // only main lang match } }); this.itexts = itexts_tmp; this.secondary_lang = secondary_lang_tmp; this.primary_lang = primary_lang_tmp; }, enable: function(lang) { if (lang === 'none') { this.active = "none"; } else if (this.itexts[lang]) { this.itexts[lang].fetch(); this.active = lang; } } }; // class to parse all itextlists under a media element // and create a menu // would need to be implemented inside Browser var ItextCollection = function (video, div_id) { this.init(video, div_id); }; ItextCollection.prototype = { video: null, div_id: null, itextlist: {}, init: function (vid, div) { this.video = vid; this.div_id = div; this.load(); // set up display divs for each category for (var i in this.itextlist) { jQuery("#" + this.div_id).append("
"); } }, load: function () { // go through each itextlist, parse it and add it to itextlist{} var tracks_tmp = this.video.find('itextlist'); var itextlist_tmp={}; tracks_tmp.each(function (i) { // create the itextlist track var ilist = new ItextList(jQuery(this)); itextlist_tmp[ilist.category] = {}; itextlist_tmp[ilist.category] = ilist; }); this.itextlist = itextlist_tmp; }, itextMenu: function (baseEl, elstring) { var appendText = '\n'; jQuery(baseEl).append(appendText); var videoHeight = jQuery(this.video).css("height").substr(0, jQuery(this.video).css("height").length - 2); jQuery(".langMenu").css("height", "240px"); jQuery(".catMenu").css("visibility", "hidden"); }, show: function (currentTime) { // add to correct content container var mc_width = jQuery('.mc').css("width").substr(0, jQuery('.mc').css("width").length - 2); // get content per category, if active, and display it var content = null; for (var i in this.itextlist) { var li = this.itextlist[i]; // get content for active tracks only if (li.active != "none") { content = li.itexts[li.active].currentText(currentTime); // update content & styling of itext div if necessary if (content) { if (jQuery("#" + this.div_id + " > .itext_" + i).html() !== content) { // update content and make it visible jQuery("#" + this.div_id + " > .itext_" + i).html(content); jQuery("#" + this.div_id + " > .itext_" + i).css("visibility", "visible"); // update styling dependent on text length if (i === "CUE") { jQuery("#" + this.div_id + " > .itext_" + i + " > .text").prepend("Chapter: "); } if (i === "TAD") { jQuery("#" + this.div_id + " > .itext_TAD").css("max-width", (mc_width) + "px"); } if (i === "LRC") { jQuery("#" + this.div_id + " > .itext_LRC").css("max-width", mc_width + "px"); // somehow the setting of "left" encourages the correct width to be calculated // if I don't do that, the width calculation in text_half_length is too short on some elements jQuery("#" + this.div_id + " > .itext_LRC").css("left",5); var text_half_length = jQuery("#" + this.div_id + " > .itext_LRC > .text").css("width").substr(0, jQuery("#" + this.div_id + " > .itext_LRC > .text").css("width").length - 2) / 2; jQuery("#" + this.div_id + " > .itext_LRC").css("left", ((mc_width / 2) - text_half_length - 7) + "px"); } if (i === "CC" || i === "SUB" || i === "KTV" || i === "TRX" || i === "LIN") { // anyone with a better idea for how to place the captions bottom center, please speak up jQuery("#" + this.div_id + " > .itext_" + i).css("max-width", mc_width + "px"); // somehow the setting of "left" encourages the correct width to be calculated // if I don't do that, the width calculation in text_half_length is too short on some elements jQuery("#" + this.div_id + " > .itext_" + i).css("left",5); var text_half_length = jQuery("#" + this.div_id + " > .itext_" + i + " > .text").css("width").substr(0, jQuery("#" + this.div_id + " > .itext_" + i + " > .text").css("width").length - 2) / 2; jQuery("#" + this.div_id + " > .itext_" + i).css("left", ((mc_width / 2) - text_half_length - 7) + "px"); } } } else { // remove content jQuery("#" + this.div_id + " > .itext_" + i).css("visibility", "hidden"); } } else { // remove content jQuery("#" + this.div_id + " > .itext_" + i).css("visibility", "hidden"); } } } };