ovenplayer
Version:
OvenPlayer is Open-Source HTML5 Player. OvenPlayer supports WebRTC Signaling from OvenMediaEngine for Sub-Second Latency Streaming.
351 lines (329 loc) • 14.3 kB
JavaScript
import {getBrowserLanguage} from "utils/browser";
/*
* sami-parser
* The MIT License (MIT)
*
* Copyright (c) 2013 Constantine Kim <elegantcoder@gmail.com>
* https://github.com/elegantcoder/sami-parser
*
*/
const langCodes = ["ab","aa","af", "ak", "sq", "am", "ar", "an", "hy", "as", "av", "ae", "ay", "az", "bm", "ba", "eu", "be", "bn", "bh", "bi", "nb","bs","br","bg","my","es","ca","km","ch","ce","ny","ny","zh","za","cu","cu","cv","kw",
"co","cr","hr","cs","da","dv","dv","nl","dz","en","eo","et","ee","fo","fj","fi","nl","fr","ff","gd","gl","lg","ka","de","ki","el","kl","gn","gu","ht","ht","ha","he","hz","hi","ho","hu","is","io","ig","id","ia","ie","iu","ik","ga",
"it","ja","jv","kl","kn","kr","ks","kk","ki","rw","ky","kv","kg","ko","kj","ku","kj","ky","lo","la","lv","lb","li","li","li","ln","lt","lu","lb","mk","mg","ms","ml","dv","mt","gv","mi","mr","mh","ro","ro","mn","na","nv","nv","nd",
"nr","ng","ne","nd","se","no","nb","nn","ii","ny","nn","ie","oc","oj","cu","cu","cu","or","om","os","os","pi","pa","ps","fa","pl","pt","pa","ps","qu","ro","rm","rn","ru","sm","sg","sa","sc","gd","sr","sn","ii","sd","si","si","sk",
"sl","so","st","nr","es","su","sw","ss","sv","tl","ty","tg","ta","tt","te","th","bo","ti","to","ts","tn","tr","tk","tw","ug","uk","ur","ug","uz","ca","ve","vi","vo","wa","cy","fy","wo","xh","yi","yo","za","zu"];
const reOpenSync = /<sync/i;
const reCloseSync = /<sync|<\/body|<\/sami/i;
const reLineEnding = /\r\n?|\n/g;
const reBrokenTag = /<[a-z]*[^>]*<[a-z]*/g;
const reStartTime = /<sync[^>]+?start[^=]*=[^0-9]*([0-9]*)["^0-9"]*/i;
const reBr = /<br[^>]*>/ig;
const reStyle = /<style[^>]*>([\s\S]*?)<\/style[^>]*>/i;
const reComment = /(<!--|-->)/g;
const clone = function(obj) {
var flags, key, newInstance;
if ((obj == null) || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof RegExp) {
flags = '';
if (obj.global != null) {
flags += 'g';
}
if (obj.ignoreCase != null) {
flags += 'i';
}
if (obj.multiline != null) {
flags += 'm';
}
if (obj.sticky != null) {
flags += 'y';
}
return new RegExp(obj.source, flags);
}
newInstance = new obj.constructor();
for (key in obj) {
newInstance[key] = clone(obj[key]);
}
return newInstance;
};
const strip_tags = function (input, allowed) {
// http://kevin.vanzonneveld.net
// + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Luke Godfrey
// + input by: Pul
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Onno Marsman
// + input by: Alex
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + input by: Marc Palau
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + input by: Brett Zamir (http://brett-zamir.me)
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Eric Nagel
// + input by: Bobby Drake
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Tomasz Wesolowski
// + input by: Evertjan Garretsen
// + revised by: Rafał Kukawski (http://blog.kukawski.pl/)
// * example 1: strip_tags('<p>Kevin</p> <br /><b>van</b> <i>Zonneveld</i>', '<i><b>');
// * returns 1: 'Kevin <b>van</b> <i>Zonneveld</i>'
// * example 2: strip_tags('<p>Kevin <img src="someimage.png" onmouseover="someFunction()">van <i>Zonneveld</i></p>', '<p>');
// * returns 2: '<p>Kevin van Zonneveld</p>'
// * example 3: strip_tags("<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>", "<a>");
// * returns 3: '<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>'
// * example 4: strip_tags('1 < 5 5 > 1');
// * returns 4: '1 < 5 5 > 1'
// * example 5: strip_tags('1 <br/> 1');
// * returns 5: '1 1'
// * example 6: strip_tags('1 <br/> 1', '<br>');
// * returns 6: '1 1'
// * example 7: strip_tags('1 <br/> 1', '<br><br/>');
// * returns 7: '1 <br/> 1'
allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
return input.replace(commentsAndPhpTags, '').replace(tags, function($0, $1) {
return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
});
};
const _sort = function(langItem) {
return langItem.sort(function(a, b) {
var res;
if ((res = a.start - b.start) === 0) {
return a.end - b.end;
} else {
return res;
}
});
};
const _mergeMultiLanguages = function(arr) {
var content, dict, i, idx, key, lang, ret, val, _i, _len, _ref;
dict = {};
i = arr.length;
ret = [];
for (i = _i = 0, _len = arr.length; _i < _len; i = ++_i) {
val = arr[i];
key = val.startTime + ',' + val.endTime;
if ((idx = dict[key]) !== void 0) {
_ref = val.languages;
for (lang in _ref) {
content = _ref[lang];
ret[idx].languages[lang] = content;
}
} else {
ret.push(val);
dict[key] = ret.length - 1;
}
}
return ret;
};
const SmiParser = function(sami, options) {
var definedLangs, duration, errors, getDefinedLangs, getLanguage, key, makeEndTime, parse, result, value, _ref, fixedLang;
parse = function() {
var element, error, innerText, isBroken, item, lang, langItem, lineNum, nextStartTagIdx, ret, startTagIdx, startTime, str, tempRet, _ref, _ref1, _ref2;
error = function(error) {
var e;
e = new Error(error);
e.line = lineNum;
e.context = element;
return errors.push(e);
};
lineNum = 1;
ret = [];
tempRet = {};
str = sami;
while (true) {
startTagIdx = str.search();
if (nextStartTagIdx <= 0 || startTagIdx < 0) {
break;
}
nextStartTagIdx = str.slice(startTagIdx + 1).search(reCloseSync) + 1;
if (nextStartTagIdx > 0) {
element = str.slice(startTagIdx, startTagIdx + nextStartTagIdx);
} else {
element = str.slice(startTagIdx);
}
lineNum += ((_ref = str.slice(0, startTagIdx).match(reLineEnding)) != null ? _ref.length : void 0) || 0;
if (isBroken = reBrokenTag.test(element)) {
error('ERROR_BROKEN_TAGS');
}
str = str.slice(startTagIdx + nextStartTagIdx);
startTime = +((_ref1 = element.match(reStartTime)) != null ? parseFloat(_ref1[1]/1000) : void 0); //HSLEE ms -> s 로 변경
if (startTime === null || startTime < 0) {
error('ERROR_INVALID_TIME');
}
// We don't need complex language. cus SMI doens't obey the rules...
lang = getLanguage(element);
//lang = "ko";
if (!lang) {
// continue;
error('ERROR_INVALID_LANGUAGE');
}
lineNum += ((_ref2 = element.match(reLineEnding)) != null ? _ref2.length : void 0) || 0;
element = element.replace(reLineEnding, '');
element = element.replace(reBr, "\n");
innerText = strip_tags(element).trim();
//HSLEE : 20180530 - 우린 랭기지 구분이 필요 없다. 있는거 그대로 보여줄뿐
item = {
start: startTime,
//languages: {},
text: "",
contents: innerText
};
if (lang) {
//item.languages[lang] = innerText;
item.text = innerText;
}
tempRet[lang] || (tempRet[lang] = []);
//tempRet[lang].push(item);
if(item.start){
tempRet[lang].push(item);
}
}
//fixed by hslee 190130
//SMI was designed for multi language. But global standard (my guess) SRT, VTT doesn't support multi language.
//This update is handling if SMI has multiple languages.
fixedLang = fixedLang || getBrowserLanguage();
let convertedLanguageNames = Object.keys(tempRet);
if(convertedLanguageNames && convertedLanguageNames.length > 0){
if(convertedLanguageNames.indexOf(fixedLang) > -1){
langItem = tempRet[fixedLang];
}else{
langItem = tempRet[convertedLanguageNames.filter(function(name){return name !== "undefined"})[0]];
}
langItem = _sort(langItem);
langItem = makeEndTime(langItem);
ret = ret.concat(langItem);
}
//ret = _mergeMultiLanguages(ret);
ret = _sort(ret);
return ret;
};
getLanguage = function(element) {
var className, lang;
if(!element){return ;}
for (className in definedLangs) {
lang = definedLangs[className];
if (lang.reClassName.test(element)) {
return lang.lang;
}
}
};
getDefinedLangs = function() {
var className, declaration, e, error, lang, matched, parsed, rule, selector, _i, _len, _ref, _ref1, _results;
try {
matched = ((_ref = sami.match(reStyle)) != null ? _ref[1] : void 0) || '';
matched = matched.replace(reComment, '');
parsed = cssParse(matched);
_ref1 = parsed.stylesheet.rules;
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
rule = _ref1[_i];
selector = rule.selectors[0];
if ((selector != null ? selector[0] : void 0) === '.') {
_results.push((function() {
var _j, _len1, _ref2, _results1;
_ref2 = rule.declarations;
_results1 = [];
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
declaration = _ref2[_j];
if (declaration.property.toLowerCase() === 'lang') {
className = selector.slice(1);
lang = declaration.value.slice(0, 2);
if (~langCodes.indexOf(lang)) {
_results1.push(definedLangs[className] = {
lang: lang,
reClassName: new RegExp("class[^=]*?=[\"'\S]*(" + className + ")['\"\S]?", 'i')
});
} else {
throw Error();
}
} else {
_results1.push(void 0);
}
}
return _results1;
})());
} else {
_results.push(void 0);
}
}
return _results;
} catch (_error) {
e = _error;
errors.push(error = new Error('ERROR_INVALID_LANGUAGE_DEFINITION'));
}
};
makeEndTime = function(langItem) {
var i, item, _ref;
i = langItem.length;
while (i--) {
item = langItem[i];
if ((_ref = langItem[i - 1]) != null) {
//HSLEE : 이왕이면 SRT 파서와 포맷을 맞추자
_ref.end = item.start;
}
if (!item.contents || item.contents === ' ') {
langItem.splice(i, 1);
} else {
delete langItem[i].contents;
if (!item.end) {
item.end = item.start + duration;
}
}
}
return langItem;
};
errors = [];
definedLangs = {
KRCC: {
lang: 'ko',
reClassName: new RegExp("class[^=]*?=[\"'\S]*(KRCC)['\"\S]?", 'i')
},
KOCC: {
lang: 'ko',
reClassName: new RegExp("class[^=]*?=[\"'\S]*(KOCC)['\"\S]?", 'i')
},
KR: {
lang: 'ko',
reClassName: new RegExp("class[^=]*?=[\"'\S]*(KR)['\"\S]?", 'i')
},
ENCC: {
lang: 'en',
reClassName: new RegExp("class[^=]*?=[\"'\S]*(ENCC)['\"\S]?", 'i')
},
EGCC: {
lang: 'en',
reClassName: new RegExp("class[^=]*?=[\"'\S]*(EGCC)['\"\S]?", 'i')
},
EN: {
lang: 'en',
reClassName: new RegExp("class[^=]*?=[\"'\S]*(EN)['\"\S]?", 'i')
},
JPCC: {
lang: 'ja',
reClassName: new RegExp("class[^=]*?=[\"'\S]*(JPCC)['\"\S]?", 'i')
}
};
if (options != null ? options.definedLangs : void 0) {
_ref = options.definedLangs;
for (key in _ref) {
value = _ref[key];
definedLangs[key] = value;
}
}
duration = (options != null ? options.duration : void 0) || 10; //HSLEE ms -> s 로 변경
fixedLang = options.fixedLang;
sami = sami.trim();
//getDefinedLangs();
result = parse();
return {
result: result,
errors: errors
};
};
export default SmiParser;