From 07fdb094dd8e52d70806431f939dde3ab0cc0260 Mon Sep 17 00:00:00 2001 From: rolux Date: Wed, 23 Jul 2014 15:55:09 +0200 Subject: [PATCH] support audio and subtitle tracks (multiple languages etc), first round --- source/Ox.UI/js/Video/AnnotationFolder.js | 4 + source/Ox.UI/js/Video/AnnotationPanel.js | 40 ++- source/Ox.UI/js/Video/VideoAnnotationPanel.js | 81 +++-- source/Ox.UI/js/Video/VideoPlayer.js | 325 ++++++++++++------ source/Ox.UI/js/Video/VideoPlayerPanel.js | 2 + 5 files changed, 328 insertions(+), 124 deletions(-) diff --git a/source/Ox.UI/js/Video/AnnotationFolder.js b/source/Ox.UI/js/Video/AnnotationFolder.js index f08c9710..8eb60966 100644 --- a/source/Ox.UI/js/Video/AnnotationFolder.js +++ b/source/Ox.UI/js/Video/AnnotationFolder.js @@ -43,6 +43,7 @@ Ox.AnnotationFolder = function(options, self) { item: '', items: [], keyboard: '', + languages: 'all', out: 0, position: 0, range: 'all', @@ -413,6 +414,9 @@ Ox.AnnotationFolder = function(options, self) { && item['in'] <= self.options.position && item.out >= self.options.position ) + ) && ( + self.options.languages == 'all' + || self.options.languages.indexOf(item.language) > -1 ) && ( self.options.users == 'all' || self.options.users.indexOf(item.user) > -1 diff --git a/source/Ox.UI/js/Video/AnnotationPanel.js b/source/Ox.UI/js/Video/AnnotationPanel.js index 8a40d77e..55f827dd 100644 --- a/source/Ox.UI/js/Video/AnnotationPanel.js +++ b/source/Ox.UI/js/Video/AnnotationPanel.js @@ -91,6 +91,11 @@ Ox.AnnotationPanel = function(options, self) { self.editing = false; + self.languages = getLanguages(); + self.enabledLanguages = self.languages.map(function(language) { + return language.code; + }); + if (self.options.showUsers) { self.users = getUsers(); self.enabledUsers = self.users; @@ -170,6 +175,21 @@ Ox.AnnotationPanel = function(options, self) { return folder; } + function getLanguages() { + return Ox.sortBy(Ox.map(Ox.unique(Ox.flatten( + self.options.layers.map(function(layer) { + return layer.items.map(function(item) { + return item.languages; + }); + }) + )), function(language) { + return { + code: language, + name: Ox.getLanguageNameByCode(language) + }; + }), 'name'); + } + function getUsers() { return Ox.sort(Ox.unique(Ox.flatten( self.options.layers.map(function(layer) { @@ -419,6 +439,15 @@ Ox.AnnotationPanel = function(options, self) { {id: 'text', title: Ox._('By Text'), checked: self.options.sort == 'text'} ]} ], + self.languages.length > 1 ? [ + {}, + {id: 'languages', title: Ox._('Show Languages'), disabled: true}, + {group: 'languages', min: 1, max: -1, items: self.languages.map(function(language) { + return {id: language.code, title: Ox._(language.name), checked: + self.enabledLanguages.indexOf(language.code) > -1 + }; + })} + ] : [], self.options.showUsers && self.users.length ? [ {}, {id: 'users', title: Ox._('Show Users'), disabled: true}, @@ -438,7 +467,14 @@ Ox.AnnotationPanel = function(options, self) { .bindEvent({ change: function(data) { var set = {}; - if (data.id == 'users') { + if (data.id == 'languages') { + self.enabledLanguages = data.checked.map(function(checked) { + return checked.id; + }); + self.$folder.forEach(function($folder) { + $folder.options({languages: self.enabledLanguages}); + }); + } else if (data.id == 'users') { self.enabledUsers = data.checked.map(function(checked) { return checked.id; }); @@ -529,6 +565,7 @@ Ox.AnnotationPanel = function(options, self) { // called from addannotation callback var i = Ox.getIndexById(self.options.layers, layer); self.$folder[i].addItem(item); + self.languages = getLanguages(); self.users = getUsers(); if (self.enabledUsers != 'all' && self.enabledUsers.indexOf(item.user) == -1) { self.enabledUsers.push(item.user); @@ -584,6 +621,7 @@ Ox.AnnotationPanel = function(options, self) { } else { // called from removeannotation callback self.options.selected = ''; + self.languages = getLanguages(); self.users = getUsers(); renderOptionsMenu(); renderEditMenu(); diff --git a/source/Ox.UI/js/Video/VideoAnnotationPanel.js b/source/Ox.UI/js/Video/VideoAnnotationPanel.js index 0b63298e..8403830d 100644 --- a/source/Ox.UI/js/Video/VideoAnnotationPanel.js +++ b/source/Ox.UI/js/Video/VideoAnnotationPanel.js @@ -86,6 +86,7 @@ Ox.VideoAnnotationPanel = function(options, self) { showLayers: {}, showUsers: false, subtitles: [], + subtitlesDefaultTrack: 'en', subtitlesLayer: null, timeline: '', timelines: [], @@ -282,6 +283,31 @@ Ox.VideoAnnotationPanel = function(options, self) { self.options.subtitles = self.options.subtitles || getSubtitles(); + if (Ox.isObject(self.options.video[0])) { + self.resolutions = Ox.sort(Ox.unique( + self.options.video.map(function(video) { + return video.resolution; + }) + )).map(function(resolution) { + return { + id: resolution + '', + title: resolution + 'p', + checked: self.options.resolution == resolution + }; + }); + self.audioTracks = Ox.sort(Ox.unique( + self.options.video.map(function(video) { + return video.track; + }) + )).map(function(track) { + return { + id: track, + title: Ox._(Ox.getLanguageNameByCode(video.track)), + checked: self.options.audioTrack == track + }; + }); + } + self.options.layers.forEach(function(layer, i) { that.bindEvent('key_' + (i + 1), function() { layer.editable @@ -350,6 +376,7 @@ Ox.VideoAnnotationPanel = function(options, self) { showMilliseconds: 3, sizeIsLarge: self.options.videoSize == 'large', subtitles: Ox.clone(self.options.subtitles, true), + subtitlesDefaultTrack: self.options.subtitlesDefaultTrack, type: type, video: type == 'play' ? self.options.video : self.options.getFrameURL, volume: self.options.volume, @@ -487,16 +514,6 @@ Ox.VideoAnnotationPanel = function(options, self) { } }); - self.resolutions = []; - Ox.forEach(self.options.video, function(url, resolution) { - Ox.Log('Video', url, resolution); - self.resolutions.push({ - id: resolution + '', - title: resolution + 'p', - checked: self.options.resolution == resolution - }); - }); - self.$keyboardShortcuts = $('
').css({margin: '16px'}); [ {key: Ox.UI.symbols.space, action: Ox._('Play/Pause')}, @@ -564,24 +581,30 @@ Ox.VideoAnnotationPanel = function(options, self) { {id: 'size', title: Ox._('Large Player'), checked: self.options.videoSize == 'large'}, {id: 'loop', title: Ox._('Loop'), checked: self.options.loop, keyboard: 'l'}, {}, - {id: 'resolutions', title: Ox._('Resolution'), disabled: true}, - {group: 'resolution', min: 1, max: 1, items: self.resolutions} + {id: 'resolutions', title: Ox._('Resolution'), items: [ + {group: 'resolution', min: 1, max: 1, items: self.resolutions} + ]} ], + self.audioTracks.length > 1 ? [ + {id: 'audioTracks', title: Ox._('Audio'), items: [ + {group: 'audioTrack', min: 1, max: 1, items: self.audioTracks} + ]} + ] : [], self.options.subtitles.length ? [ - {}, + // TODO... {id: 'subtitles', title: Ox._('Show Subtitles'), checked: self.options.enableSubtitles} ] : [], [ - {}, - {id: 'timelines', title: Ox._('Timeline'), disabled: true}, - {group: 'timeline', min: 1, max: 1, items: Ox.map( - self.options.timelines, - function(timeline) { - return Ox.extend({ - checked: timeline.id == self.options.timeline - }, timeline); - } - )}, + {id: 'timelines', title: Ox._('Timeline'), items: [ + {group: 'timeline', min: 1, max: 1, items: Ox.map( + self.options.timelines, + function(timeline) { + return Ox.extend({ + checked: timeline.id == self.options.timeline + }, timeline); + } + )} + ]}, {}, {id: 'gotoPosterFrame', title: Ox._('Go to Poster Frame'), keyboard: 'shift p'}, {id: 'setPosterFrame', title: Ox._('Set Poster Frame'), disabled: !self.options.enableSetPosterFrame}, @@ -650,7 +673,10 @@ Ox.VideoAnnotationPanel = function(options, self) { } else if (id == 'resolution') { self.options.resolution = parseInt(data.checked[0].id, 10); self.$player[0].options({resolution: self.options.resolution}); + } else if (id == 'audioTrack') { + // ... } else if (id == 'subtitles') { + // TODO... toggleSubtitles(); } else if (id == 'timeline') { self.options.timeline = data.checked[0].id; @@ -1132,7 +1158,14 @@ Ox.VideoAnnotationPanel = function(options, self) { id: subtitle.id, 'in': subtitle['in'], out: subtitle.out, - text: subtitle.value.replace(/\n/g, ' ').replace(//g, '\n') + text: subtitle.value.replace(/\n/g, ' ').replace(//g, '\n'), + tracks: ( + subtitle.languages + ? subtitle.languages + : [self.options.subtitleDefaultTrack] + ).map(function(language) { + return Ox.getLanguageNameByCode(language); + }) }; }) : []; } diff --git a/source/Ox.UI/js/Video/VideoPlayer.js b/source/Ox.UI/js/Video/VideoPlayer.js index af654ff4..9899c6fa 100644 --- a/source/Ox.UI/js/Video/VideoPlayer.js +++ b/source/Ox.UI/js/Video/VideoPlayer.js @@ -8,6 +8,8 @@ Ox.VideoPlayer Generic Video Player in In point (sec) out Out point (sec) text Text + tracks <[s]> Track names, like "English" or "Director's Commentary" + audioTrack Two-letter ISO 639-1 language code or track name censored Array of censored ranges censoredIcon 'Censored' icon censoredTooltip Tooltip for 'censored' icon @@ -68,14 +70,17 @@ Ox.VideoPlayer Generic Video Player in In point (sec) out Out point (sec) text Text + tracks <[s]> Track names, like "English" or "Director's Commentary" + subtitlesDefaultTrack Default subtitle language (ISO 639-1) or track name + subtitlesTrack Subtitle language (ISO 639-1) or track name timeline Timeline image URL timelineType Current timeline type id timelineTypes <[o]|[]> Array of timeline type objects (id and title) title Video title type 'play', 'in' or 'out' - video Video URL - String or array of strings ([part1, part2, ...]) or object - ({resolution: url, ...} or {resolution: [part1, part2, ...], ...}) + video Video URL + String or array of strings ([part1, part2, ...]) or array of objects + ({duration, index, resolution, src, track}) volume Volume (0-1) width Width in px self shared private variable @@ -111,6 +116,7 @@ Ox.VideoPlayer = function(options, self) { var that = Ox.Element({}, self) .defaults({ annotations: [], + audioTrack: '', brightness: 1, censored: [], censoredIcon: '', @@ -160,6 +166,8 @@ Ox.VideoPlayer = function(options, self) { showProgress: false, sizeIsLarge: false, subtitles: [], + subtitlesDefaultTrack: 'en', + subtitlesTrack: 'en', timeline: '', timelineType: '', timelineTypes: [], @@ -171,10 +179,8 @@ Ox.VideoPlayer = function(options, self) { }) .options(options || {}) .update({ - enableSubtitles: function() { - self.options.enableSubtitles = !self.options.enableSubtitles; - toggleSubtitles(); - }, + audioTrack: setAudioTrack, + enableSubtitles: setSubtitleTrack, find: setSubtitleText, fullscreen: function() { self.options.fullscreen = !self.options.fullscreen; @@ -215,6 +221,7 @@ Ox.VideoPlayer = function(options, self) { loadSubtitles(); self.$settings && self.$settings.replaceWith(self.$settings = renderSettings()); }, + subtitlesTrack: setSubtitlesTrack, timeline: function() { self.$timeline.options({imageURL: self.options.timeline}); }, @@ -231,8 +238,6 @@ Ox.VideoPlayer = function(options, self) { }) .addClass('OxVideoPlayer'); - Ox.Log('Video', 'VIDEO PLAYER OPTIONS', self.options) - Ox.UI.$window.on({ resize: function() { self.options.fullscreen && setSizes(); @@ -250,6 +255,10 @@ Ox.VideoPlayer = function(options, self) { self.options.annotations = self.options.subtitles; } + if (Ox.isObject(self.options.video[0]) && 'index' in self.options.video[0]) { + self.options.video = Ox.sortBy(self.options.video, 'index'); + } + setVideo(); if (self.options.playInToOut) { @@ -369,20 +378,20 @@ Ox.VideoPlayer = function(options, self) { ---------------------------------------------------------------------------- */ - if ( - (!self.options.externalControls && - (self.options.controlsTop.length || self.options.controlsBottom.length)) - ) { + if (( + !self.options.externalControls && ( + self.options.controlsTop.length + || self.options.controlsBottom.length + ) + )) { that.on({ mouseenter: function() { showControls(); self.mouseHasLeft = false; - //Ox.Log('Video', 'MOUSE HAS ENTERED') }, mouseleave: function() { hideControls(); self.mouseHasLeft = true; - //Ox.Log('Video', 'MOUSE HAS LEFT') } }); } @@ -396,7 +405,9 @@ Ox.VideoPlayer = function(options, self) { self.$videoContainer = Ox.Element() .addClass('OxVideoContainer') .css({ - top: self.options.externalControls && self.options.controlsTop.length ? '16px' : 0 + top: self.options.externalControls + && self.options.controlsTop.length + ? '16px' : 0 }) .appendTo(that) @@ -528,8 +539,8 @@ Ox.VideoPlayer = function(options, self) { .hide() .appendTo(self.$videoContainer); if (!Ox.isEmpty( - Ox.isObject(self.options.video) - ? self.options.video[self.options.resolution] + Ox.isObject(self.options.video[0]) + ? getVideo() : self.options.video )) { showLoadingIcon(); @@ -631,7 +642,9 @@ Ox.VideoPlayer = function(options, self) { self['$controls' + titleCase] = Ox.Bar({ size: self.barHeight }) - .addClass('OxControls' + (self.options.externalControls ? '' : ' OxOnScreen')) + .addClass('OxControls' + ( + self.options.externalControls ? '' : ' OxOnScreen' + )) .css({ opacity: self.options.externalControls ? 1 : 0 }) @@ -679,7 +692,9 @@ Ox.VideoPlayer = function(options, self) { self.$fullscreenButton = Ox.Button({ style: 'video', - tooltip: [Ox._('Enter Fullscreen'), Ox._('Exit Fullscreen')], + tooltip: [ + Ox._('Enter Fullscreen'), Ox._('Exit Fullscreen') + ], type: 'image', value: self.options.fullscreen ? 'shrink' : 'grow', values: ['grow', 'shrink'] @@ -696,7 +711,10 @@ Ox.VideoPlayer = function(options, self) { self.$setButton = Ox.Button({ style: 'video', title: 'goTo' + Ox.toTitleCase(self.options.type), - tooltip: Ox._('Go to ' + Ox.toTitleCase(self.options.type) + ' Point'), + tooltip: Ox._( + 'Go to ' + Ox.toTitleCase(self.options.type) + + ' Point' + ), type: 'image' }) .bindEvent({ @@ -801,9 +819,11 @@ Ox.VideoPlayer = function(options, self) { self.positionWidth = getPositionWidth(); self.$position = Ox.Element({ - tooltip: Ox._(self.options.type == 'play' ? 'Position' + tooltip: Ox._( + self.options.type == 'play' ? 'Position' : self.options.type == 'in' ? 'In Point' - : 'Out Point') + : 'Out Point' + ) }) .addClass('OxPosition') .css({ @@ -893,7 +913,10 @@ Ox.VideoPlayer = function(options, self) { self.$setButton = Ox.Button({ style: 'video', title: 'set' + Ox.toTitleCase(self.options.type), - tooltip: Ox._('Set ' + Ox.toTitleCase(self.options.type) + ' Point'), + tooltip: Ox._( + 'Set ' + Ox.toTitleCase(self.options.type) + + ' Point' + ), type: 'image' }) .bindEvent({ @@ -1276,9 +1299,12 @@ Ox.VideoPlayer = function(options, self) { self.posterMarkerCSS = getPosterMarkerCSS(); self.$video.css(self.videoCSS); self.$poster && self.$poster.css(self.videoCSS); - self.$posterMarker && Ox.forEach(self.$posterMarker, function(marker, position) { - marker.css(self.posterMarkerCSS[position]); - }); + self.$posterMarker && Ox.forEach( + self.$posterMarker, + function(marker, position) { + marker.css(self.posterMarkerCSS[position]); + } + ); self.out = self.options.playInToOut && self.out < self.$video.duration() ? self.out : self.$video.duration(); self.options.duration = self.out - self['in']; @@ -1482,7 +1508,8 @@ Ox.VideoPlayer = function(options, self) { }; } else if (element == 'subtitle') { css = { - bottom: (Math.floor(self.height / 16) + !!self.controlsBottomAreVisible * 16) + 'px', + bottom: (Math.floor(self.height / 16) + + !!self.controlsBottomAreVisible * 16) + 'px', width: self.width + 'px', fontSize: Math.floor(self.height / 20) + 'px', WebkitTextStroke: (self.height / 1000) + 'px rgb(0, 0, 0)' @@ -1508,19 +1535,19 @@ Ox.VideoPlayer = function(options, self) { return css; } + function getMenuSection(str) { + var match = str.match(//); + return match ? match[1] : null; + } + function getPosition(e) { // fixme: no offsetX in firefox??? if ($.browser.mozilla) { - //Ox.Log('Video', e, e.layerX - 56) return Ox.limit( (e.layerX - 48 - self.barHeight / 2) / self.timelineImageWidth * self.$video.duration(), 0, self.$video.duration() ); } else { - /*Ox.Log('Video', e.offsetX, Ox.limit( - (e.offsetX - self.barHeight / 2) / self.timelineImageWidth * self.video.duration, - 0, self.video.duration - ))*/ return Ox.limit( (e.offsetX - self.barHeight / 2) / self.timelineImageWidth * self.$video.duration(), 0, self.$video.duration() @@ -1560,7 +1587,6 @@ Ox.VideoPlayer = function(options, self) { } function getProgressImageURL() { - //Ox.Log('Video', '---', self.timelineImageWidth) if (!self.timelineImageWidth) return; var width = self.timelineImageWidth, height = self.barHeight, @@ -1595,6 +1621,7 @@ Ox.VideoPlayer = function(options, self) { if ( v['in'] <= self.options.position && v.out >= self.options.position + && v.track == self.options.subtitlesTrack ) { subtitle = v.text; return false; // break @@ -1603,6 +1630,14 @@ Ox.VideoPlayer = function(options, self) { return subtitle; } + function getSubtitles() { + return self.options.enableSubtitles + ? self.options.subtitles.map(function(v) { + return v.track == self.options.subtitlesTrack; + }) + : []; + } + function getTimeline() { var $timeline = Ox.SmallVideoTimeline({ //_offset: getTimelineLeft(), @@ -1634,7 +1669,6 @@ Ox.VideoPlayer = function(options, self) { }); } }); - //Ox.Log('Video', '??', $timeline.find('.OxInterface')) $timeline.children().css({ marginLeft: getTimelineLeft() + 'px' }); @@ -1668,8 +1702,8 @@ Ox.VideoPlayer = function(options, self) { } function getTitleWidth() { - return (self.options.fullscreen ? window.innerWidth : self.options.width) - - self.options.controlsTop.reduce(function(prev, curr) { + return (self.options.fullscreen ? window.innerWidth : self.options.width) + - self.options.controlsTop.reduce(function(prev, curr) { return prev + ( curr == 'title' || curr == 'chapterTitle' || curr == 'space' ? 0 : Ox.startsWith(curr, 'space') ? parseInt(curr.substr(5)) @@ -1678,6 +1712,13 @@ Ox.VideoPlayer = function(options, self) { }, 0); } + function getVideo() { + return self.options.video.filter(function(video) { + return video.audioTrack == self.options.audioTrack + && video.resolution == self.options.resolution; + }); + } + function getVideoCSS(videoWidth, videoHeight) { // optional arguments allow for this function to be used for poster CSS var playerWidth = self.width, @@ -1758,7 +1799,11 @@ Ox.VideoPlayer = function(options, self) { //Ox.Log('Video', 'hideControls'); clearTimeout(self.interfaceTimeout); self.interfaceTimeout = setTimeout(function() { - if (!self.exitFullscreen && !self.inputHasFocus && !self.mouseIsInControls) { + if ( + !self.exitFullscreen + && !self.inputHasFocus + && !self.mouseIsInControls + ) { self.interfaceIsVisible = false; self.controlsTopAreVisible = false; self.controlsBottomAreVisible = false; @@ -1812,10 +1857,6 @@ Ox.VideoPlayer = function(options, self) { return Math.abs(a - b) < 0.001; } - function isResolution(str) { - return str.slice(0, -1).match(/^\d+$/) && str.slice(-1) == 'p'; - } - function itemchange(data) { var item = self.$video.options('items')[data.item]; Ox.Log('Video', 'ITEMCHANGE', item); @@ -1856,9 +1897,12 @@ Ox.VideoPlayer = function(options, self) { self.posterMarkerCSS = getPosterMarkerCSS(); self.$video.css(self.videoCSS); self.$poster && self.$poster.css(self.videoCSS); - self.$posterMarker && Ox.forEach(self.$posterMarker, function(marker, position) { - marker.css(self.posterMarkerCSS[position]); - }); + self.$posterMarker && Ox.forEach( + self.$posterMarker, + function(marker, position) { + marker.css(self.posterMarkerCSS[position]); + } + ); self.out = self.options.playInToOut && self.out < self.$video.duration() ? self.out : self.$video.duration(); @@ -1899,6 +1943,15 @@ Ox.VideoPlayer = function(options, self) { } function loadedsubtitles() { + if (!self.subtitlesTracks || Ox.isEmpty(self.subtitlesTracks)) { + self.subtitlesTracks = [{ + code: self.options.subtitlesDefaultTrack, + name: Ox.getLanguageNameByCode( + self.options.subtitlesDefaultTrack + ) + }]; + } + self.subtitlesTracks.push({code: '', name: 'None'}); if (self.options.find) { submitFindInput(self.options.find); if (self.options.duration) { @@ -1917,6 +1970,21 @@ Ox.VideoPlayer = function(options, self) { function loadSubtitles() { if (self.options.subtitles) { if (Ox.isArray(self.options.subtitles)) { + self.subtitlesTracks = Ox.sortBy( + Ox.unique( + self.options.subtitles.map(function(subtitle) { + return subtitle.track; + }).filter(function(track) { + return !!track; + }) + ).map(function(track) { + return { + code: track, + name: Ox.getLanguageNameByCode(track) + }; + }), + 'name' + ); loadedsubtitles(); } else { if (self.options.subtitles.indexOf('\n') > -1) { @@ -1982,24 +2050,32 @@ Ox.VideoPlayer = function(options, self) { } function renderSettings() { + // fixme: use proper ids (as class of span) var $settings = $('
') .addClass('OxControls OxSettings') .on({ click: function(e) { - var $target = $(e.target), resolution, title, type; + var $target = $(e.target), resolution, title, track, type; self.$settings.hide(); if (!$target.is('.OxLine') && !$target.is('.OxSpace')) { title = $(e.target).parent().children()[0].innerHTML; if (title == Ox._('Download')) { that.triggerEvent('download'); - } else if (title == Ox._('Subtitles')) { - toggleSubtitles(); - } else if (isResolution(title)) { - resolution = parseInt(title, 10); + } else if (getMenuSection(title) == 'resolution') { + resolution = parseInt(Ox.stripTags(title), 10); if (resolution != self.options.resolution) { self.options.resolution = resolution; setResolution(); } + } else if (getMenuSection(title) == 'audioTrack') { + track = Ox.stripTags(title); + self.options.audioTrack = Ox.getLanguageCodeByName(track); + setAudioTrack(); + } else if (getMenuSection(title) == 'subtitlesTrack') { + track = Ox.stripTags(title); + self.options.subtitlesTrack = track == 'None' + ? '' : Ox.getLanguageCodeByName(track); + setSubtitleTrack(); } else { type = self.options.timelineTypes[ Ox.indexOf(self.options.timelineTypes, function(type) { @@ -2037,33 +2113,49 @@ Ox.VideoPlayer = function(options, self) { } }), items = [{ - disabled: true, - title: Ox._('Resolution') - }].concat( + disabled: true, + title: Ox._('Resolution') + }].concat( self.resolutions.map(function(resolution) { return { checked: resolution == self.options.resolution, - title: resolution + 'p' + title: '' + + resolution + 'p' }; }), + self.audioTracks.length > 1 + ? [{}, {disabled: true, title: Ox._('Audio')}].concat( + self.audioTracks.map(function(track) { + return { + checked: track.code == self.options.audioTrack, + title: '' + + Ox._(track.name) + '' + }; + }) + ) + : [], self.options.subtitles.length - ? [{}, { - checked: self.options.enableSubtitles, - title: Ox._('Subtitles') - }] + ? [{}, {disabled: true, title: Ox._('Subtitles')}].concat( + self.subtitlesTracks.map(function(track) { + return { + checked: track.code == self.options.enableSubtitles + ? self.options.subtitlesTrack : '', + title: '' + + Ox._(track.name) + '' + }; + }) + ) : [], self.options.timelineTypes.length - ? [{}, { - disabled: true, - title: Ox._('Timeline') - }] : [], - self.options.timelineTypes.length - ? self.options.timelineTypes.map(function(type) { - return { - checked: type.id == self.options.timelineType, - title: type.title - }; - }) + ? [{}, {disabled: true, title: Ox._('Timeline')}].concat( + self.options.timelineTypes.map(function(type) { + return { + checked: type.id == self.options.timelineType, + title: '' + + type.title + '' + }; + }) + ) : [], self.options.enableDownload ? [{}, {title: Ox._('Download')}] @@ -2130,6 +2222,10 @@ Ox.VideoPlayer = function(options, self) { } } + function setAudioTrack() { + // ... + } + function setCensored() { var censored = getCensored(); if (censored != self.censored) { @@ -2152,15 +2248,17 @@ Ox.VideoPlayer = function(options, self) { isEqual(self.options.position, self.options.posterFrame) ? marker.show() : marker.hide(); }); - self.$pointMarker && Ox.forEach(self.$pointMarker, function(markers, point) { - Ox.forEach(markers, function(marker) { - //Ox.Log('Video', self.options.position, self.options[point], isEqual(self.options.position, self.options[point])) - // fixme: there's a bug in jquery and/or webkit - // on load, show() doesn't work - isEqual(self.options.position, self.options[point]) - ? marker.css({display: 'block'}) : marker.hide(); - }); - }); + self.$pointMarker && Ox.forEach( + self.$pointMarker, + function(markers, point) { + Ox.forEach(markers, function(marker) { + // fixme: there's a bug in jquery and/or webkit + // on load, show() doesn't work + isEqual(self.options.position, self.options[point]) + ? marker.css({display: 'block'}) : marker.hide(); + }); + } + ); } function setPoint() { @@ -2206,7 +2304,7 @@ Ox.VideoPlayer = function(options, self) { } self.loadedMetadata = false; showLoadingIcon(); - self.video = self.options.video[self.options.resolution]; + self.video = getVideo(); self.$video.options({ items: self.video }); @@ -2230,8 +2328,10 @@ Ox.VideoPlayer = function(options, self) { } function setSizes(animate, callback) { - self.width = self.options.fullscreen ? window.innerWidth : self.options.width; - self.height = self.options.fullscreen ? window.innerHeight : self.options.height; + self.width = self.options.fullscreen + ? window.innerWidth : self.options.width; + self.height = self.options.fullscreen + ? window.innerHeight : self.options.height; self.videoCSS = getVideoCSS(); self.iconSize = Ox.limit(Math.round(self.height / 10), 16, 32); if (self.$timeline || self.$spaceBottom) { @@ -2293,24 +2393,59 @@ Ox.VideoPlayer = function(options, self) { function setSubtitleText() { self.$subtitle.html( self.subtitle - ? Ox.highlight(self.subtitle, self.options.find, 'OxHighlight', true) - .replace(/\n/g, '
') + ? Ox.highlight( + self.subtitle, self.options.find, 'OxHighlight', true + ).replace(/\n/g, '
') : ' 
 ' // FIXME: weird bug, only in fullscreen, only in chrome ); } + function setSubtitlesTrack() { + var enableSubtitles = !!self.options.subtitlesTrack; + setSubtitle(); + self.$timeline && self.$timeline.options({ + subtitles: getSubtitles() + }); + if (enableSubtitles != self.options.enableSubtitles) { + self.options.enableSubtitles = enableSubtitles; + that.triggerEvent('subtitles', { + subtitles: self.options.enableSubtitles + }); + } + } + function setTimelineType() { that.triggerEvent('timeline', {timeline: self.options.timelineType}); } function setVideo() { - if (Ox.isObject(self.options.video)) { - self.resolutions = Ox.sort(Object.keys(self.options.video)); - if (!(self.options.resolution in self.options.video)) { + if (Ox.isObject(self.options.video[0])) { + self.audioTracks = Ox.sortBy(Ox.unique( + self.options.video.map(function(video) { + return { + code: video.track, + name: Ox.getLanguageNameByCode(video.track) + }; + }) + ), 'name'); + if (!Ox.contains( + self.audioTracks.map(function(track) { + return track.code; + }), + self.options.audioTrack + )) { + self.options.audioTrack = self.audioTracks[0].code; + } + self.resolutions = Ox.sort(Ox.unique( + self.options.video.map(function(video) { + return video.resolution; + }) + )); + if (!Ox.contains(self.resolutions, self.options.resolution)) { self.options.resolution = self.resolutions[0]; } - self.video = self.options.video[self.options.resolution]; + self.video = getVideo(); } else { self.video = self.options.video; } @@ -2578,7 +2713,10 @@ Ox.VideoPlayer = function(options, self) { self.playInToOut = false; } else { hidePoster(); - if (self.options.playInToOut && self.options.position > self.options.out - self.secondsPerFrame) { + if ( + self.options.playInToOut + && self.options.position > self.options.out - self.secondsPerFrame + ) { setPosition(self.options['in']); } self.$video.play(); @@ -2639,17 +2777,6 @@ Ox.VideoPlayer = function(options, self) { }); } - function toggleSubtitles() { - self.options.enableSubtitles = !self.options.enableSubtitles; - setSubtitle(); - self.$timeline && self.$timeline.options({ - subtitles: self.options.enableSubtitles ? self.options.subtitles : [] - }); - that.triggerEvent('subtitles', { - subtitles: self.options.enableSubtitles - }); - } - function toggleVolume() { self.$volume.toggle(); } diff --git a/source/Ox.UI/js/Video/VideoPlayerPanel.js b/source/Ox.UI/js/Video/VideoPlayerPanel.js index 08b0997d..71cd12c7 100644 --- a/source/Ox.UI/js/Video/VideoPlayerPanel.js +++ b/source/Ox.UI/js/Video/VideoPlayerPanel.js @@ -74,6 +74,7 @@ Ox.VideoPlayerPanel = function(options, self) { showUsers: false, smallTimelineURL: '', subtitles: [], + subtitlesDefaultTrack: 'en', timeline: '', timelineTooltip: 'timeline', video: '', @@ -227,6 +228,7 @@ Ox.VideoPlayerPanel = function(options, self) { resolution: self.options.resolution, scaleToFill: self.options.scaleToFill, subtitles: self.options.subtitles, + subtitlesDefaultTrack: self.options.subtitlesDefaultTrack, timeline: self.options.smallTimelineURL, video: self.options.video, volume: self.options.volume,