'use strict'; /*@ Ox.VideoPlayer <f> Generic Video Player options <o> Options annotations <[]> Array of annotations id <s> Optional id in <n> In point (sec) out <n> Out point (sec) text <s> Text tracks <[s]> Track names, like "English" or "Director's Commentary" audioTrack <s|''> Two-letter ISO 639-1 language code or track name censored <a|[]> Array of censored ranges censoredIcon <s|''> 'Censored' icon censoredTooltip <s|''> Tooltip for 'censored' icon chapters <[o]|[]> List of chapter objects with position and title controlsBottom <[s]|[]> Bottom controls, from left to right Can be 'close', fullscreen', 'scale', 'title', 'chapterTitle', 'find', 'open', 'play', 'playInToOut', 'previous', 'next', 'loop', 'mute', 'volume', 'size', 'timeline', 'position', 'settings', and 'space[int]'. A 'space16' control, for example, is empty space that is 16px wide, and a 'space' control is empty space that separates left-aligned from right-aligned controls. controlsTooltips <o|{}> Tooltip text per control id controlsTop <[s]|[]> Top controls, from left to right duration <n|-1> Duration (sec) enableDownload <b|false> If true, enable download enableFind <b|false> If true, enable find enableFullscreen <b|false> If true, enable fullscreen enableKeyboard <b|false> If true, enable keyboard controls enableMouse <b|false> If true, click toggles paused and drag changes position enablePosition <b|false> If true, enable position control enableSubtitles <b|false> If true, enable subtitles externalControls <b|false> If true, controls are outside the video enableTimeline <b|false> If true, show timeline find <s|''> Query string focus <s|'click'> focus on 'click', 'load' or 'mouseenter' format <s|''> video format (like 'webm') fps <n|25> Frames per second fullscreen <b|false> If true, video is in fullscreen height <n|144> Height in px (excluding external controls) in <n> In point (sec) invertHighlight <b|false> If true, invert selection highlight on timeline logo <s|''> Logo image URL logoLink <s|''> Logo link URL logoTitle <s|''> Text for Logo tooltip // fixme: shouldn't this be logoTooltip then?s loop <b|false> If true, video loops muted <b|false> If true, video is muted paused <b|false> If true, video is paused playbackRate: <n|1> playback rate playInToOut <b|false> If true, video plays only from in to out position <n|0> Initial position (sec) poster <s|''> Poster URL posterFrame <n|-1> Position of poster frame (sec) out <n> Out point (sec) resolution <n|0> resolution rewind <b|false> If true, video will rewind when ended scaleToFill <b|false> If true, scale to fill (otherwise, scale to fit) showControlsOnLoad <b|false> If true, show controls on load showFind <b|false> If true, show find input showHours <b|false> If true, don't show hours for videos shorter than one hour showIcon <b|false> If true, show play icon showIconOnLoad <b|false> If true, show icon on load showLargeTimeline <b|false> If true, show large timeline showMilliseconds <n|0> Number of decimals to show showMarkers <b|false> If true, show in/out/poster markers showProgress <|false> If true, show buffering progress sizeIsLarge <b|false> If true, initial state of the size control is large subtitles <s|[o]|[]> URL or SRT or array of subtitles id <s> Optional id in <n> In point (sec) out <n> Out point (sec) text <s> Text tracks <[s]> Track names, like "English" or "Director's Commentary" subtitlesOffset <n|0> bottom offset for subtitles in % of video height subtitlesDefaultTrack <s|'English'> Track name subtitlesTrack <s|'English'> Track name timeline <s> Timeline image URL timelineType <s|''> Current timeline type id timelineTypes <[o]|[]> Array of timeline type objects (id and title) title <s|''> Video title type <s|'play'> 'play', 'in' or 'out' video <s|[s]|[o]|''> Video URL String or array of strings ([part1, part2, ...]) or array of objects ({duration, index, resolution, src, track}) volume <n|1> Volume (0-1) width <n|256> Width in px self <o> shared private variable ([options[, self]]) -> <o:Ox.Element> Video Player censored <!> censored close <!> close download <!> download ended <!> ended find <!> find fullscreen <!> fullscreen gotopoint <!> gotopoint loadedmetadata <!> loadedmetadata muted <!> muted open <!> open paused <!> paused playing <!> playing position <!> position positioning <!> positioning resolution <!> resolution scale <!> scale select <!> select setpoint <!> setpoint size <!> size subtitles <!> subtitles timeline <!> timeline volume <!> volume zap <!> zap @*/ Ox.VideoPlayer = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ annotations: [], audioTrack: '', brightness: 1, censored: [], censoredIcon: '', censoredTooltip: '', controlsBottom: [], controlsTooltips: {}, controlsTop: [], duration: 0, enableDownload: false, enableFind: false, enableFullscreen: false, enableKeyboard: false, enableMouse: false, enablePosition: false, enableSubtitles: false, enableTimeline: false, externalControls: false, find: '', focus: 'click', format: '', fps: 25, fullscreen: false, height: 144, 'in': 0, logo: '', logoLink: '', logoTitle: '', loop: false, muted: false, paused: false, playbackRate: 1, playInToOut: false, position: 0, poster: '', posterFrame: -1, out: 0, resolution: 0, rewind: false, scaleToFill: false, showControlsOnLoad: false, showFind: false, showHours: false, showIcon: false, showIconOnLoad: false, showLargeTimeline: false, showMarkers: false, showMilliseconds: 0, showProgress: false, sizeIsLarge: false, subtitles: [], subtitlesDefaultTrack: 'English', subtitlesOffset: 0, subtitlesTrack: 'English', timeline: '', timelineType: '', timelineTypes: [], title: '', type: 'play', video: '', volume: 1, width: 256 }) .options(options || {}) .update({ audioTrack: setAudioTrack, enableSubtitles: setSubtitlesTrack, find: setSubtitleText, fullscreen: function() { self.options.fullscreen = !self.options.fullscreen; toggleFullscreen(); }, height: function() { setSizes(); }, 'in': function() { self.options.paused && setMarkers(); self.$timeline && self.$timeline.options('in', self.options['in']); }, out: function() { self.options.paused && setMarkers(); self.$timeline && self.$timeline.options({out: self.options.out}); }, muted: function() { self.options.muted = !self.options.muted; toggleMuted(); }, paused: function() { self.options.paused = !self.options.paused; togglePaused(); }, playbackRate: function() { self.$video.options({ playbackRate: self.options.playbackRate }); }, position: function() { setPosition(self.options.position); }, posterFrame: function() { self.options.paused && setMarkers(); }, resolution: setResolution, scaleToFill: function() { self.options.scaleToFill = !self.options.scaleToFill; toggleScale(); }, sizeIsLarge: function() { self.$sizeButton.toggle(); }, subtitles: function() { loadSubtitles(); self.$settings && self.$settings.replaceWith(self.$settings = renderSettings()); }, subtitlesTrack: setSubtitlesTrack, timeline: function() { self.$timeline.options({imageURL: self.options.timeline}); }, video: function() { setVideo(); self.$video.options({ items: self.video }); }, volume: function() { setVolume(self.options.volume); }, width: function() { setSizes(); } }) .addClass('OxVideoPlayer'); Ox.$window.on({ resize: function() { self.options.fullscreen && setSizes(); } }); Ox.Fullscreen.bind('change', function() { //FIXME: is change fired before window size is updated to fullscreen? setTimeout(function() { setSizes(self.options.fullscreen); }, 250); }); if (Ox.isEmpty(self.options.annotations)) { 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) { self['in'] = self.options['in']; self.out = self.options.out; self.options.duration = self.out - self['in']; } else { self['in'] = 0; self.out = self.options.duration || 86399; // fixme: ugly } self.options.position = Ox.limit(self.options.position, self['in'], self.out); if (!self.options.paused) { self.options.showIconOnLoad = false; } self.hasVolumeControl = self.options.controlsTop.indexOf('volume') > -1 || self.options.controlsBottom.indexOf('volume') > -1; self.millisecondsPerFrame = 1000 / self.options.fps; self.secondsPerFrame = 1 / self.options.fps; self.barHeight = 16; self.width = self.options.fullscreen ? window.innerWidth : self.options.width; self.height = self.options.fullscreen ? window.innerHeight : self.options.height; self.videoWidth = self.options.width; self.videoHeight = self.options.height; self.results = []; loadSubtitles(); /* ---------------------------------------------------------------------------- Keyboard ---------------------------------------------------------------------------- */ if (self.options.enableKeyboard) { that.bindEvent({ key_0: toggleMuted, key_1: toggleScale, key_down: function() { goToNext('chapter', 1); }, key_equal: function() { changeVolume(0.1); }, key_escape: hideControlMenus, key_f: focusFind, key_g: function() { goToNext('result', 1); }, key_k: function togglePlaybackRate() { that.options({ playbackRate: self.options.playbackRate == 1 ? 2 : self.options.playbackRate == 2 ? 0.5 : 1 }); }, key_l: toggleLoop, key_left: function() { setPosition(self.options.position - self.secondsPerFrame); that.triggerEvent('position', { position: self.options.position }); }, key_minus: function() { changeVolume(-0.1); }, key_p: playInToOut, key_right: function() { setPosition(self.options.position + self.secondsPerFrame); that.triggerEvent('position', { position: self.options.position }); }, key_shift_f: function() { self.options.enableFullscreen && toggleFullscreen(); }, key_shift_g: function() { goToNext('result', -1); }, key_shift_left: function() { setPosition(self.options.position - 1); that.triggerEvent('position', { position: self.options.position }); }, key_shift_right: function() { setPosition(self.options.position + 1); that.triggerEvent('position', { position: self.options.position }); }, key_space: togglePaused, key_up: function() { goToNext('chapter', -1); } }); if (self.options.focus == 'mouseenter') { that.on({ mouseenter: function() { if (!self.inputHasFocus) { that.gainFocus(); } }, mouseleave: function() { that.loseFocus(); } }); } else { that.on({ click: function() { if (!Ox.Focus.focusedElementIsInput()) { that.gainFocus(); } } }); } } /* ---------------------------------------------------------------------------- Mouse ---------------------------------------------------------------------------- */ if (( !self.options.externalControls && ( self.options.controlsTop.length || self.options.controlsBottom.length ) )) { that.on({ mouseenter: function() { showControls(); self.mouseHasLeft = false; }, mouseleave: function() { hideControls(); self.mouseHasLeft = true; } }); } /* ---------------------------------------------------------------------------- Video ---------------------------------------------------------------------------- */ self.$videoContainer = Ox.Element() .addClass('OxVideoContainer') .css({ top: self.options.externalControls && self.options.controlsTop.length ? '16px' : 0 }) .appendTo(that) if (self.options.type == 'play') { self.options.enableMouse && self.$videoContainer.bindEvent({ anyclick: function(e) { var $target = $(e.target); if (!$target.is('.OxLogo') && !$target.is('.OxCensoredIcon')) { togglePaused(); } }, dragstart: dragstart, drag: drag, dragend: dragend }); self.$video = Ox.VideoElement({ items: self.video, loop: self.options.loop, muted: self.options.muted, playbackRate: self.options.playbackRate, volume: self.options.volume }) .bindEvent(Ox.extend({ durationchange: durationchange, ended: ended, itemchange: itemchange, loadedmetadata: loadedmetadata, requiresusergesture: requiresusergesture, seeked: seeked, seeking: seeking, sizechange: sizechange }, self.options.progress ? { progress: progress } : {})) .appendTo(self.$videoContainer); self.$video.$element.css({position: 'absolute'}); // avoid showing first frame self.$video.hide(); } else { self.options.enableMouse && self.$videoContainer.on({ click: function(e) { if (!$(e.target).is('.OxLogo')) { goToPoint(); } } }); self.$video = $('<div>') .appendTo(self.$videoContainer); self.$image = $('<img>') .attr({ src: Ox.UI.PATH + 'png/transparent.png' }) .css({ position: 'absolute', width: '100%', height: '100%' }) .appendTo(self.$video) self.$brightness = $('<div>') .css({ position: 'absolute', width: '100%', height: '100%', background: 'rgb(0, 0, 0)', opacity: 1 - self.options.brightness }) .appendTo(self.$video); } /* ---------------------------------------------------------------------------- Poster ---------------------------------------------------------------------------- */ if (self.options.poster) { self.$poster = $('<img>') .addClass('OxPoster') .attr({ src: self.options.poster }) .hide() .one({ load: function() { self.$poster .css(getVideoCSS( self.$poster[0].width, self.$poster[0].height )) .show(); self.posterIsVisible = true; } }) .appendTo(self.$videoContainer); } /* ---------------------------------------------------------------------------- Logo ---------------------------------------------------------------------------- */ if (self.options.logo) { self.$logo = $('<img>') .addClass('OxLogo') .attr({ src: self.options.logo }) .css({ cursor: self.options.logoLink ? 'pointer' : 'default' }) .appendTo(self.$videoContainer); if (self.options.logoTitle) { self.$logoTooltip = Ox.Tooltip({ title: self.options.logoTitle }); } } /* ---------------------------------------------------------------------------- Icons ---------------------------------------------------------------------------- */ self.$loadingIcon = Ox.LoadingIcon({video: true}) .hide() .appendTo(self.$videoContainer); if (!Ox.isEmpty( Ox.isObject(self.options.video[0]) ? getVideo() : self.options.video )) { showLoadingIcon(); } if (self.options.showIcon || self.options.showIconOnLoad) { self.$playIcon = $('<img>') .addClass('OxPlayIcon OxVideo') .attr({ src: Ox.UI.getImageURL('symbol' + ( self.options.paused ? 'Play' : 'Pause' ), 'videoIcon') }) .appendTo(self.$videoContainer); if (self.options.showIcon) { self.$playIcon.addClass('OxInterface'); } if (self.options.showIconOnLoad) { self.iconIsVisible = true; } } if (self.options.censored.length) { self.$copyrightIcon = Ox.Element({ element: '<img>', tooltip: self.options.censoredTooltip }) .addClass('OxCensoredIcon OxVideo') .attr({ src: Ox.UI.getImageURL( 'symbol' + self.options.censoredIcon, 'videoIcon' ) }) .hide() .bindEvent({ singleclick: function() { that.triggerEvent('censored'); } }) .appendTo(self.$videoContainer); } /* ---------------------------------------------------------------------------- Markers ---------------------------------------------------------------------------- */ if (self.options.showMarkers) { self.$posterMarker = {}; ['left', 'center', 'right'].forEach(function(position) { var titleCase = Ox.toTitleCase(position); self.$posterMarker[position] = $('<div>') .addClass('OxPosterMarker OxPosterMarker' + titleCase) .appendTo(self.$videoContainer); }); self.$pointMarker = {}; ['in', 'out'].forEach(function(point) { self.$pointMarker[point] = {}; ['top', 'bottom'].forEach(function(edge) { var titleCase = Ox.toTitleCase(point) + Ox.toTitleCase(edge); self.$pointMarker[point][edge] = $('<img>') .addClass('OxPointMarker OxPointMarker' + titleCase) .attr({ src: Ox.UI.getImageURL('marker' + titleCase) }) .appendTo(self.$videoContainer); }); }); } /* ---------------------------------------------------------------------------- Subtitles ---------------------------------------------------------------------------- */ if (self.options.subtitles.length || true) { // FIXME self.$subtitle = $('<div>') .addClass('OxSubtitle') .appendTo(self.$videoContainer); } /* ---------------------------------------------------------------------------- Controls ---------------------------------------------------------------------------- */ ['top', 'bottom'].forEach(function(edge) { var titleCase = Ox.toTitleCase(edge); if (self.options['controls' + titleCase].length) { self['$controls' + titleCase] = Ox.Bar({ size: self.barHeight }) .addClass('OxControls' + ( self.options.externalControls ? '' : ' OxOnScreen' )) .css({ opacity: self.options.externalControls ? 1 : 0 }) .css(edge, 0) .appendTo(that); self.options['controls' + titleCase].forEach(function(control) { if (control == 'chapterTitle') { self.$chapterTitle = $('<div>') .addClass('OxTitle') .html(getChapterTitle()) .appendTo(self['$controls' + titleCase].$element); } else if (control == 'close') { self.$closeButton = Ox.Button({ style: 'video', title: 'close', tooltip: Ox._('Close'), type: 'image' }) .bindEvent({ click: function() { that.triggerEvent('close'); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'find') { self.$findButton = Ox.Button({ style: 'video', title: 'find', tooltip: Ox._('Find'), type: 'image' }) .bindEvent({ click: toggleFind }) .appendTo(self['$controls' + titleCase]); } else if (control == 'fullscreen') { self.$fullscreenButton = Ox.Button({ style: 'video', tooltip: [ Ox._('Enter Fullscreen'), Ox._('Exit Fullscreen') ], type: 'image', value: self.options.fullscreen ? 'shrink' : 'grow', values: ['grow', 'shrink'] }) .bindEvent({ click: function() { toggleFullscreen('button'); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'goto') { self.$setButton = Ox.Button({ style: 'video', title: 'goTo' + Ox.toTitleCase(self.options.type), tooltip: Ox._( 'Go to ' + Ox.toTitleCase(self.options.type) + ' Point' ), type: 'image' }) .bindEvent({ click: goToPoint }) .appendTo(self['$controls' + titleCase]); } else if (control == 'loop') { self.$loopButton = Ox.Button({ style: 'video', tooltip: [Ox._('Don\'t Loop'), Ox._('Loop')], type: 'image', value: self.options.loop ? 'RepeatAll' : 'RepeatNone', values: ['RepeatAll', 'RepeatNone'] }) .bindEvent({ click: function() { toggleLoop('button'); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'mute') { self.$muteButton = Ox.Button({ style: 'video', tooltip: [Ox._('Mute'), Ox._('Unmute')], type: 'image', value: self.options.muted ? 'unmute' : 'mute', values: ['mute', 'unmute'] }) .bindEvent({ click: function() { toggleMuted('button'); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'next') { self.$nextChapterButton = Ox.Button({ style: 'video', title: 'playNext', tooltip: Ox._('Next'), type: 'image' }) .bindEvent({ click: function() { goToNext('chapter', 1); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'open') { self.$openButton = Ox.Button({ style: 'video', title: 'arrowRight', tooltip: self.options.controlsTooltips.open || '', type: 'image' }) .bindEvent({ click: function() { that.triggerEvent('open'); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'play') { self.$playButton = Ox.Button({ style: 'video', // FIXME: this is retarded, fix Ox.Button tooltip: [Ox._('Play'), Ox._('Pause')], type: 'image', value: self.options.paused ? 'play' : 'pause', values: ['play', 'pause'] }) .bindEvent({ click: function() { togglePaused('button'); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'playInToOut') { self.$playInToOutButton = Ox.Button({ style: 'video', title: 'playInToOut', tooltip: Ox._('Play In to Out'), type: 'image' }) .bindEvent({ click: playInToOut }) .appendTo(self['$controls' + titleCase]); } else if (control == 'position') { self.positionWidth = getPositionWidth(); self.$position = Ox.Element({ tooltip: Ox._( self.options.type == 'play' ? 'Position' : self.options.type == 'in' ? 'In Point' : 'Out Point' ) }) .addClass('OxPosition') .css({ width: self.positionWidth - 4 + 'px' }) .html(formatPosition()) .on({ click: function() { if (self.options.enablePosition) { if (self.options.type == 'play') { if (!self.options.paused) { self.playOnSubmit = true; togglePaused(); } else if (self.playOnLoad) { // if clicked during resolution switch, // don't play on load self.playOnLoad = false; self.playOnSubmit = true; } } self.$position.hide(); self.$positionInput .value(formatPosition()) .show() .focusInput(false); } } }) .appendTo(self['$controls' + titleCase]); self.$positionInput = Ox.Input({ value: formatPosition(), width: self.positionWidth }) .addClass('OxPositionInput') .bindEvent({ focus: function() { self.inputHasFocus = true; }, blur: function() { self.inputHasFocus = false; submitPositionInput(); }, submit: function() { self.inputHasFocus = false; submitPositionInput(); } }) .appendTo(self['$controls' + titleCase].$element); self.$positionInput.children('input').css({ width: (self.positionWidth - 6) + 'px', fontSize: '9px' }); } else if (control == 'previous') { self.$previousChapterButton = Ox.Button({ style: 'video', title: 'playPrevious', tooltip: Ox._('Previous'), type: 'image' }) .bindEvent({ click: function() { goToNext('chapter', -1); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'scale') { self.$scaleButton = Ox.Button({ style: 'video', tooltip: [Ox._('Scale to Fill'), Ox._('Scale to Fit')], type: 'image', value: self.options.scaleToFill ? 'fit' : 'fill', values: ['fill', 'fit'] }) .bindEvent('change', function() { toggleScale('button'); }) .appendTo(self['$controls' + titleCase]); } else if (control == 'set') { self.$setButton = Ox.Button({ style: 'video', title: 'set' + Ox.toTitleCase(self.options.type), tooltip: Ox._( 'Set ' + Ox.toTitleCase(self.options.type) + ' Point' ), type: 'image' }) .bindEvent({ click: setPoint }) .appendTo(self['$controls' + titleCase]); } else if (control == 'settings') { self.$settingsButton = Ox.Button({ style: 'video', title: 'set', tooltip: Ox._('Settings'), type: 'image' }) .bindEvent({ click: function() { self.$settings.toggle(); } }) .appendTo(self['$controls' + titleCase]); self.$settings = renderSettings().appendTo(that); } else if (control == 'size') { self.$sizeButton = Ox.Button({ style: 'video', tooltip: [Ox._('Larger'), Ox._('Smaller')], type: 'image', value: self.options.sizeIsLarge ? 'shrink' : 'grow', values: ['grow', 'shrink'] }) .bindEvent('change', toggleSize) .appendTo(self['$controls' + titleCase]); } else if (control == 'space') { self['$space' + titleCase] = $('<div>') .html(' ') // fixme: ?? .appendTo(self['$controls' + titleCase].$element); } else if (Ox.startsWith(control, 'space')) { $('<div>') .css({ width: parseInt(control.substr(5)) + 'px', height: '16px' }) .appendTo(self['$controls' + titleCase].$element); } else if (control == 'timeline') { /* if (self.options.showProgress) { self.$progress = $('<img>') .attr({ src: getProgressImageURL() }) .css({ float: 'left', height: self.barHeight + 'px', }) .appendTo(self.$timelineImages.$element); } */ if (self.options.duration) { self.$timeline = getTimeline() } else { self.$timeline = Ox.Element() .html(' '); } self.$timeline.appendTo(self['$controls' + titleCase]); } else if (control == 'title') { self.$title = $('<div>') .addClass('OxTitle') .html(self.options.title) .appendTo(self['$controls' + titleCase].$element); } else if (control == 'volume') { self.$volumeButton = Ox.Button({ style: 'video', title: getVolumeImage(), tooltip: Ox._('Volume'), type: 'image' }) .bindEvent({ click: toggleVolume }) .appendTo(self['$controls' + titleCase]); } else if (control == 'zapHome') { self.$zapHomeButton = Ox.Button({ style: 'video', title: 'up', tooltip: Ox._('Home Channel'), type: 'image' }) .bindEvent({ click: function() { that.triggerEvent('zap', {direction: 0}); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'zapNext') { self.$zapNextButton = Ox.Button({ style: 'video', title: 'right', tooltip: Ox._('Next Channel'), type: 'image' }) .bindEvent({ click: function() { that.triggerEvent('zap', {direction: 1}); } }) .appendTo(self['$controls' + titleCase]); } else if (control == 'zapPrevious') { self.$zapPreviousButton = Ox.Button({ style: 'video', title: 'left', tooltip: Ox._('Previous Channel'), type: 'image' }) .bindEvent({ click: function() { that.triggerEvent('zap', {direction: -1}); } }) .appendTo(self['$controls' + titleCase]); } }); } }); /* ---------------------------------------------------------------------------- Find ---------------------------------------------------------------------------- */ if (self.options.enableFind) { self.$find = $('<div>') .addClass('OxControls OxFind') .css({ top: self.options.controlsTop.length ? '16px' : 0 }) .appendTo(that); self.$results = Ox.Element({ tooltip: Ox._('Results') }) .addClass('OxResults') .html('0') .appendTo(self.$find); self.$previousResultButton = Ox.Button({ disabled: true, style: 'symbol', title: 'arrowLeft', tooltip: Ox._('Previous'), type: 'image' }) .bindEvent({ click: function() { goToNext('result', -1); } }) .appendTo(self.$find); self.$nextResultButton = Ox.Button({ disabled: true, style: 'symbol', title: 'arrowRight', tooltip: Ox._('Next'), type: 'image' }) .bindEvent({ click: function() { goToNext('result', 1); } }) .appendTo(self.$find); self.$findInput = Ox.Input({ changeOnKeypress: true, value: self.options.find }) .bindEvent({ blur: function() { self.inputHasFocus = false; }, focus: function() { self.inputHasFocus = true; }, change: function(data) { submitFindInput(data.value, false); }, submit: function(data) { self.inputHasFocus = false; submitFindInput(data.value, true); } }) .appendTo(self.$find); self.$clearButton = Ox.Button({ disabled: !self.options.find, style: 'symbol', title: 'delete', tooltip: Ox._('Clear'), type: 'image' }) .bindEvent({ click: function() { self.options.find = ''; self.results = []; self.$results.html('0'); self.$findInput.clearInput(); self.subtitle && setSubtitleText(); self.$timeline && self.$timeline.options({ find: self.options.find, results: self.results }); //setTimeout(self.$findInput.focusInput, 10); } }) .appendTo(self.$find); self.$hideFindButton = Ox.Button({ style: 'symbol', title: 'close', tooltip: Ox._('Hide'), type: 'image' }) .bindEvent({ click: toggleFind }) .appendTo(self.$find); } /* ---------------------------------------------------------------------------- Volume ---------------------------------------------------------------------------- */ if (self.hasVolumeControl) { self.$volume = $('<div>') .addClass('OxControls OxVolume') .css({ bottom: self.options.controlsBottom.length ? '16px' : 0 }) .appendTo(that); self.$hideVolumeButton = Ox.Button({ style: 'symbol', title: 'close', tooltip: Ox._('Hide'), type: 'image' }) .bindEvent({ click: toggleVolume }) .appendTo(self.$volume); self.$muteButton = Ox.Button({ style: 'symbol', tooltip: [Ox._('Mute'), Ox._('Unmute')], type: 'image', value: self.options.muted ? 'unmute' : 'mute', values: ['mute', 'unmute'] }) .bindEvent({ click: function() { toggleMuted('button'); } }) .appendTo(self.$volume); self.$volumeInput = Ox.Range({ changeOnDrag: true, max: 1, min: 0, step: 0.001, value: self.options.muted ? 0 : self.options.volume }) .bindEvent({ change: function(data) { setVolume(data.value); } }) .appendTo(self.$volume); self.$volumeValue = $('<div>') .addClass('OxVolumeValue') .html(self.options.muted ? 0 : Math.round(self.options.volume * 100)) .appendTo(self.$volume); } self.options.type != 'play' && setPosition(self.options.position); self.results = []; setSizes(false, function() { self.options.fullscreen && enterFullscreen(); }); function censor() { if (self.options.type == 'play') { self.$video .brightness(self.censored ? 0.05 : self.options.brightness) .volume(self.censored ? 0.01 : self.options.volume); } else { self.$brightness.css({ opacity: 1 - (self.censored ? 0.05 : self.options.brightness) }); } self.$copyrightIcon[self.censored ? 'show' : 'hide'](); } function changeVolume(num) { self.hasVolumeControl && showVolume(); self.options.volume = Ox.limit(self.options.volume + num, 0, 1); setVolume(self.options.volume); self.$volumeInput && self.$volumeInput.value(self.options.volume); } function clearInterfaceTimeout() { clearTimeout(self.interfaceTimeout); self.interfaceTimeout = 0; } function dragstart() { Ox.$body.addClass('OxDragging'); self.drag = { position: self.options.position, paused: self.options.paused } !self.options.paused && togglePaused(); } function drag(e) { setPosition(self.drag.position - e.clientDX / 25); that.triggerEvent('positioning', { position: self.options.position }); } function dragend() { Ox.$body.removeClass('OxDragging'); !self.drag.paused && togglePaused(); that.triggerEvent('position', { position: self.options.position }); } function durationchange() { self.videoWidth = self.$video.videoWidth(); self.videoHeight = self.$video.videoHeight(); self.videoCSS = getVideoCSS(); 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.out = self.options.playInToOut && self.out < self.$video.duration() ? self.out : self.$video.duration(); self.options.duration = self.out - self['in']; self.$timeline && self.$timeline.replaceWith( self.$timeline = getTimeline() ); if(self.loadedMetadata) { setPosition(self.$video.currentTime()); } else { self.loadedMetadata = true; setPosition(self.options.position); if (self.options.paused && self.playOnLoad) { self.playOnLoad = false; togglePaused('button'); } } self.$subtitle && self.$subtitle.animate({ bottom: getCSS('subtitle').bottom }, 250); that.triggerEvent('durationchange', { duration: self.options.duration }); } function ended() { !self.options.paused && togglePaused(); if (self.options.poster) { self.$poster.animate({ opacity: 1 }, 250); self.posterIsVisible = true; } if (self.options.showIconOnLoad) { self.$playIcon.animate({ opacity: 1 }, 250); self.iconIsVisible = true; } self.options.rewind && setTimeout(rewind, 250); that.triggerEvent('ended'); } function enterFullscreen() { that.on({ mousemove: function() { showControls(); hideControls(); } }); showControls(); hideControls(); that.find('.OxControls').on({ mouseenter: function() { self.mouseIsInControls = true; }, mouseleave: function() { self.mouseIsInControls = false; } }); that.gainFocus(); } function find(query) { var results = []; if (query.length) { query = query.toLowerCase(); results = Ox.filter(self.options.annotations, function(annotation) { return Ox.decodeHTMLEntities(Ox.stripTags( annotation.text.toLowerCase() )).indexOf(query) > -1; }).map(function(annotation) { return { id: annotation.id, 'in': annotation['in'], out: annotation.out }; }) results = Ox.filter(self.options.annotations, function(annotation) { return Ox.decodeHTMLEntities(Ox.stripTags( annotation.text.toLowerCase() )).indexOf(query) > -1; }).map(function(annotation) { return { id: annotation.id, 'in': annotation['in'], out: annotation.out }; }); } return results; } function focusFind() { !self.interfaceIsVisible && showControls(); // need timeout so the "f" doesn't appear in the input field setTimeout(function() { if (self.$find.is(':hidden')) { toggleFind(); } else { self.$findInput.focusInput(true); } }, 0); } function formatPosition(position) { position = Ox.isUndefined(position) ? self.options.position : position; return Ox.formatDuration(position, self.options.showMilliseconds); } function getCensored() { var censored = false; Ox.forEach(self.options.censored, function(v) { if ( v['in'] < self.options.position && v.out > self.options.position ) { censored = true; return false; // break } }); return censored; } function getChapterTitle() { var chapterTitle = ''; self.options.chapters && Ox.forEach(self.options.chapters, function(v, i) { if ( v.position <= self.options.position && ( i == self.options.chapters.length - 1 || self.options.chapters[i + 1].position > self.options.position ) ) { chapterTitle = v.title; return false; // break } }); return chapterTitle; } function getCSS(element) { var css; if (element == 'copyrightIcon') { css = { width: self.iconSize + 'px', height: self.iconSize + 'px' }; } else if (element == 'controlsTop' || element == 'controlsBottom') { css = { width: self.width + 'px' }; } else if (element == 'find') { css = { width: Math.min(216, self.width) + 'px' // 128 + 4 * 16 + 24 }; } else if (element == 'loadingIcon') { css = { width: self.iconSize + 'px', height: self.iconSize + 'px' }; } else if (element == 'logo') { var logoHeight = Math.round(self.height / 10), logoMargin = Math.round(self.height / 20); css = { left: logoMargin + 'px', top: logoMargin + (self.controlsTopAreVisible ? 16 : 0) + 'px', height: logoHeight + 'px' }; } else if (element == 'player') { var height = self.options.fullscreen ? window.innerHeight : self.height; if (self.options.externalControls) { height += ( !!self.options.controlsTop.length + !!self.options.controlsBottom.length ) * self.barHeight; } css = Ox.extend({ width: self.width + 'px', height: height + 'px' }, self.options.fullscreen ? { left: 0, top: 0 } : {}, self.exitFullscreen ? { left: self.absoluteOffset.left, top: self.absoluteOffset.top } : {}); } else if (element == 'playIcon') { var playIconPadding = Math.round(self.iconSize * 1/8), playIconSize = self.iconSize - 2 * playIconPadding - 4; css = { width: playIconSize + 'px', height: playIconSize + 'px', padding: playIconPadding + 'px', borderRadius: Math.round(self.iconSize / 2) + 'px' }; } else if (element == 'progress') { css = { width: self.timelineImageWidth + 'px', marginLeft: -self.timelineImageWidth + 'px' }; } else if (element == 'subtitle') { var offset = 0, videoCSS; if (self.options.subtitlesOffset) { videoCSS = getVideoCSS(); offset = Math.floor(( self.options.subtitlesOffset / 100 ) * videoCSS.height) + videoCSS.top; offset = Math.max(offset, 0); } css = { bottom: ( Math.floor(self.height / 16) + offset + (!!self.controlsBottomAreVisible * 16) ) + 'px', width: self.width + 'px', fontSize: Math.floor(self.height / 20) + 'px', WebkitTextStroke: (self.height / 1000) + 'px rgb(0, 0, 0)' }; } else if (element == 'spaceBottom' || element == 'timeline') { css = { width: self.timelineWidth + 'px' }; } else if (element == 'spaceTop' || element == 'title') { css = { width: getTitleWidth() + 'px' }; } else if (element == 'videoContainer') { css = { width: self.width + 'px', height: self.height + 'px' }; } else if (element == 'volume') { css = { width: Math.min(184, self.width) }; } return css; } function getPosition(e) { // fixme: no offsetX in firefox??? if ($.browser.mozilla) { return Ox.limit( (e.layerX - 48 - self.barHeight / 2) / self.timelineImageWidth * self.$video.duration(), 0, self.$video.duration() ); } else { return Ox.limit( (e.offsetX - self.barHeight / 2) / self.timelineImageWidth * self.$video.duration(), 0, self.$video.duration() ); } } function getPositionWidth() { return 48 + !!self.options.showMilliseconds * 2 + self.options.showMilliseconds * 6; } function getPosterMarkerCSS() { self.videoCSS = getVideoCSS(); var left = Math.floor((self.videoCSS.width - self.videoCSS.height) / 2), right = Math.ceil((self.videoCSS.width - self.videoCSS.height) / 2); return { center: { left: self.videoCSS.left + left + 'px', top: self.videoCSS.top + 'px', width: (self.videoCSS.height - 2) + 'px', height: (self.videoCSS.height - 2) + 'px' }, left: { left: self.videoCSS.left + 'px', top: self.videoCSS.top + 'px', width: left + 'px', height: self.videoCSS.height + 'px' }, right: { left: self.videoCSS.left + left + self.videoCSS.height + 'px', top: self.videoCSS.top + 'px', width: right + 'px', height: self.videoCSS.height + 'px' } }; } function getProgressImageURL() { if (!self.timelineImageWidth) return; var width = self.timelineImageWidth, height = self.barHeight, canvas = $('<canvas>') .attr({ width: width, height: height })[0], context = canvas.getContext('2d'), imageData, data; context.fillStyle = 'rgba(255, 0, 0, 0.5)'; context.fillRect(0, 0, width, height); imageData = context.getImageData(0, 0, width, height), data = imageData.data; self.buffered.forEach(function(range) { var left = Math.round(range[0] * width / self.$video.duration()), right = Math.round(range[1] * width / self.$video.duration()); Ox.loop(left, right, function(x) { Ox.loop(height, function(y) { index = x * 4 + y * 4 * width; data[index + 3] = 0; }); }); }); context.putImageData(imageData, 0, 0); return canvas.toDataURL(); } function getSubtitle() { var subtitle = ''; if (self.options.enableSubtitles) { Ox.forEach(self.options.subtitles, function(v) { if ( v['in'] <= self.options.position && v.out >= self.options.position && (!v.tracks || Ox.contains(v.tracks, self.options.subtitlesTrack)) ) { subtitle = v.text; return false; // break } }); } return subtitle; } function getSubtitles() { return self.options.enableSubtitles ? self.options.subtitles.filter(function(v) { return !v.tracks || Ox.contains(v.tracks, self.options.subtitlesTrack); }) : []; } function getTimeline() { var $timeline = Ox.SmallVideoTimeline({ //_offset: getTimelineLeft(), disabled: !self.options.enableTimeline, duration: self.options.duration, find: self.options.find, imageURL: self.options.timeline, 'in': self.options['in'], invertHighlight: self.options.invertHighlight, mode: 'player', out: self.options.out, paused: self.options.paused, position: self.options.position, results: self.results, showInToOut: self.options.playInToOut, showMilliseconds: self.options.showMilliseconds, subtitles: self.options.enableSubtitles ? self.options.subtitles : [], width: getTimelineWidth() }) .css({float: 'left'}) .css({background: '-moz-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'}) .css({background: '-o-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'}) .css({background: '-webkit-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'}) .bindEvent({ position: function(data) { setPosition(data.position, 'timeline'); that.triggerEvent('position', { position: self.options.position }); } }); $timeline.children().css({ marginLeft: getTimelineLeft() + 'px' }); $timeline.find('.OxInterface').css({ marginLeft: getTimelineLeft() + 'px' }); return $timeline; } function getTimelineLeft() { var left = 0; Ox.forEach(self.options.controlsBottom, function(control) { if (control == 'timeline') { return false; // break } left += control == 'position' ? self.positionWidth : 16 }); return left; } function getTimelineWidth() { return (self.options.fullscreen ? window.innerWidth : self.options.width) - self.options.controlsBottom.reduce(function(prev, curr) { return prev + ( curr == 'timeline' || curr == 'space' ? 0 : Ox.startsWith(curr, 'space') ? parseInt(curr.substr(5)) : curr == 'position' ? getPositionWidth() : 16 ); }, 0); } function getTitleWidth() { 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)) : 16 ); }, 0); } function getVideo() { return self.options.video.filter(function(video) { return ( !self.options.audioTrack || video.track == self.options.audioTrack ) && ( !self.options.resolution || 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, playerHeight = self.height, playerRatio = playerWidth / playerHeight, videoWidth = videoWidth || self.videoWidth, videoHeight = videoHeight || self.videoHeight, videoRatio = videoWidth / videoHeight, videoIsWider = videoRatio > playerRatio, width, height; if (self.options.scaleToFill) { width = videoIsWider ? playerHeight * videoRatio : playerWidth; height = videoIsWider ? playerHeight : playerWidth / videoRatio; } else { width = videoIsWider ? playerWidth : playerHeight * videoRatio; height = videoIsWider ? playerWidth / videoRatio : playerHeight; } width = Math.round(width); height = Math.round(height); return { left: Math.floor((playerWidth - width) / 2), top: Math.floor((playerHeight - height) / 2), width: width, height: height }; } function getVolumeImage() { var symbol; if (self.options.muted || self.options.volume == 0) { symbol = 'Unmute'; } else if (self.options.volume < 1/3) { symbol = 'VolumeUp'; } else if (self.options.volume < 2/3) { symbol = 'VolumeDown'; } else { symbol = 'Mute'; } return symbol; } function goToNext(type, direction) { // type can be 'chapter' or 'result' var position, positions; if (type == 'chapter' && self.options.chapters) { positions = self.options.chapters.map(function(chapter) { return chapter.position; }); } else if (type == 'result' && self.results) { positions = Ox.unique(self.results.map(function(result) { return result['in']; })); } if (positions) { position = Ox.nextValue(positions, self.options.position, direction); setPosition(position); that.triggerEvent('position', { position: self.options.position }); } } function goToPoint() { that.triggerEvent('gotopoint'); } function hideControlMenus() { ['find', 'settings', 'volume'].forEach(function(element) { var $element = self['$' + element]; $element && $element.is(':visible') && $element.animate({ opacity: 0 }, 250, function() { $element.hide().css({opacity: 1}); }); }); //self.options.fullscreen && hideControls(); } function hideControls() { //Ox.Log('Video', 'hideControls'); clearTimeout(self.interfaceTimeout); self.interfaceTimeout = setTimeout(function() { if ( !self.exitFullscreen && !self.inputHasFocus && !self.mouseIsInControls ) { self.interfaceIsVisible = false; self.controlsTopAreVisible = false; self.controlsBottomAreVisible = false; self.$controlsTop && self.$controlsTop.animate({ opacity: 0 }, 250); self.$controlsBottom && self.$controlsBottom.animate({ opacity: 0 }, 250); hideControlMenus(); self.$logo && self.$logo.animate({ top: getCSS('logo').top, opacity: 0.25 }, 250, function() { self.options.logoLink && self.$logo.off('click'); self.options.logoTitle && self.$logo.off('mouseenter mouseleave'); }); self.$subtitle && self.$subtitle.animate({ bottom: getCSS('subtitle').bottom }, 250); } }, self.options.fullscreen ? 2500 : 1000); } function hideLoadingIcon() { self.$loadingIcon.hide().stop(); } function hideMarkers() { self.$posterMarker && Ox.forEach(self.$posterMarker, function(marker) { marker.hide(); }); self.$pointMarker && Ox.forEach(self.$pointMarker, function(markers) { Ox.forEach(markers, function(marker) { marker.hide(); }); }); } function hidePoster() { if (self.loadedMetadata && self.posterIsVisible) { self.$poster.animate({ opacity: 0 }, 250); self.posterIsVisible = false; } } function isEqual(a, b) { return Math.abs(a - b) < 0.001; } function itemchange(data) { var item = self.$video.options('items')[data.item]; Ox.Log('Video', 'ITEMCHANGE', item); } function requiresusergesture() { Ox.Log('Video', 'requires user gesture'); var $playIcon; function removeBehaviorsRestrictions() { window.removeEventListener('keydown', removeBehaviorsRestrictions); window.removeEventListener('mousedown', removeBehaviorsRestrictions); window.removeEventListener('touchstart', removeBehaviorsRestrictions); $playIcon.remove(); showLoadingIcon(); self.options.showIconOnLoad = false; } window.addEventListener('keydown', removeBehaviorsRestrictions); window.addEventListener('mousedown', removeBehaviorsRestrictions); window.addEventListener('touchstart', removeBehaviorsRestrictions); hideLoadingIcon(); //FIXME: also load frame at current position? $playIcon = $('<img>') .addClass('OxPlayIcon OxVideo OxInterface') .attr({ src: Ox.UI.getImageURL('symbolPlay', 'videoIcon') }) .css(getCSS('playIcon')) .css({ opacity: 1 }) .appendTo(self.$videoContainer); } function loadImage() { self.$image .one({ load: function() { hideLoadingIcon(); } }) .attr({ src: self.options.video( // fixme: this keeps the frame from being beyond the end, // but what should be avoided is setting position to a point // beyond the beginning of the last frame Math.min( self.options.position, Math.floor(self.options.duration * self.options.fps) / self.options.fps ), self.options.width ) }); } function loadedmetadata() { Ox.Log('Video', 'LOADEDMETADATA') var hadDuration = !!self.options.duration; self.loadedMetadata = true; self.videoWidth = self.$video.videoWidth(); self.videoHeight = self.$video.videoHeight(); self.videoCSS = getVideoCSS(); 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.out = self.options.playInToOut && self.out < self.$video.duration() ? self.out : self.$video.duration(); self.options.duration = self.out - self['in']; Ox.Log('Video', '---------------------------------------- POS', self.options.position) Ox.Log('Video', '----------------------------------- DURATION', self.options.duration) //self.options.position = Ox.limit(self.options.position, self['in'], self.out); //self.$video.currentTime(self.options.position); setPosition(self.options.position); self.$video.muted(self.options.muted).volume(self.options.volume); if (!self.options.paused) { self.options.paused = true; togglePaused('loadedmetadata'); } else if (self.options.paused && self.playOnLoad) { togglePaused('loadedmetadata'); } self.$playButton && self.$playButton.options({disabled: false}); if (self.options.showIcon || self.options.showIconOnLoad) { self.$playIcon.animate({ opacity: 1 }, 250); } if (self.options.showControlsOnLoad) { showControls(); } !hadDuration && self.$timeline && self.$timeline.replaceWith( self.$timeline = getTimeline() ); if (self.options.enableKeyboard && self.options.focus == 'load') { that.gainFocus(); } self.$subtitle && self.$subtitle.animate({ bottom: getCSS('subtitle').bottom }, 250); that.triggerEvent('loadedmetadata'); } function loadedsubtitles() { if (!self.subtitlesTracks || Ox.isEmpty(self.subtitlesTracks)) { self.subtitlesTracks = [self.options.subtitlesDefaultTrack]; } self.subtitlesTracks.push('None'); if (self.options.find) { submitFindInput(self.options.find); if (self.options.duration) { // duration was known or video has loaded before subtitles self.$timeline && self.$timeline.options({ results: self.results, subtitles: self.options.subtitles }); } } else { // needed on options change self.options.enableSubtitles && self.$subtitle && setSubtitle(); } } function loadSubtitles() { if (self.options.subtitles.length) { if (Ox.isArray(self.options.subtitles)) { self.subtitlesTracks = Ox.sort(Ox.unique(Ox.flatten( self.options.subtitles.map(function(subtitle) { return subtitle.tracks; }) ))).filter(function(track) { return !!track; }); loadedsubtitles(); } else { if (self.options.subtitles.indexOf('\n') > -1) { self.options.subtitles = Ox.parseSRT(self.options.subtitles); loadedsubtitles(); } else { Ox.get(self.options.subtitles, function(data) { self.options.subtitles = Ox.parseSRT(data); loadedsubtitles(); }); self.options.subtitles = []; } } } } function playing() { self.options.position = self.$video.currentTime(); if ( (self.playInToOut && self.options.position >= self.options.out) || (self.options.playInToOut && self.options.position >= self.out) ) { if (self.options.loop) { setPosition(self.options['in']); self.$video.play(); } else { togglePaused(); if (self.options.rewind) { setTimeout(rewind, 250); } else { setPosition(self.options.out ? self.options.out : self.out/*, 'video'*/); } that.triggerEvent('ended'); } } else { setPosition(self.options.position, 'video'); } that.triggerEvent('playing', { position: self.options.position }); } function playInToOut() { if (self.options.out > self.options['in']) { self.playInToOut = true; setPosition(self.options['in']); self.options.paused && togglePaused(); } } function progress() { var buffered = self.$video.buffered(); for (var i = 0; i < buffered.length; i++) { self.buffered[i] = [buffered.start(i), buffered.end(i)]; // fixme: firefox weirdness if (self.buffered[i][0] > self.buffered[i][1]) { self.buffered[i][0] = 0; } } self.$progress.attr({ src: getProgressImageURL() }); } function renderSettings() { return Ox.VideoPlayerMenu({ items: [{disabled: true, title: Ox._('Resolution')}].concat( self.resolutions.map(function(resolution) { return { group: 'resolution', id: resolution, checked: resolution == self.options.resolution, title: resolution + 'p' }; }), self.audioTracks.length > 1 ? [{}, {disabled: true, title: Ox._('Audio')}].concat( self.audioTracks.map(function(track) { return { group: 'audioTrack', id: track, checked: track == self.options.audioTrack, title: Ox._(track) }; }) ) : [], self.options.subtitles.length ? [{}, {disabled: true, title: Ox._('Subtitles')}].concat( self.subtitlesTracks.map(function(track) { return { group: 'subtitlesTrack', id: track, checked: self.options.enableSubtitles ? track == self.options.subtitlesTrack : track == '', title: Ox._(track) }; }) ) : [], self.options.timelineTypes.length ? [{}, {disabled: true, title: Ox._('Timeline')}].concat( self.options.timelineTypes.map(function(type) { return { group: 'timeline', id: type.id, checked: type.id == self.options.timelineType, title: type.title }; }) ) : [], self.options.enableDownload ? [{}, {id: 'download', title: Ox._('Download')}] : [] ) }) .addClass('OxControls OxSettings') .bindEvent({ click: function(data) { var resolution, type; if (data.group == 'resolution') { resolution = parseInt(data.id, 10); if (resolution != self.options.resolution) { self.options.resolution = resolution; setResolution(); } } else if (data.group == 'audioTrack') { self.options.audioTrack = data.id; setAudioTrack(); } else if (data.group == 'subtitlesTrack') { self.options.subtitlesTrack = data.id == 'None' ? '' : data.id; self.options.enableSubtitles = !!self.options.subtitlesTrack; setSubtitlesTrack(); } else if (data.group == 'timeline') { type = self.options.timelineTypes[ Ox.indexOf(self.options.timelineTypes, function(type) { return type.title == title; }) ].id; if (type != self.options.timelineType) { self.options.timelineType = type; setTimelineType(); } } else if (data.id == 'download') { that.triggerEvent('download'); } } }); } function rewind() { setPosition(self.options.playInToOut ? self.options['in'] : 0); } function seeked() { Ox.Log('Video', 'seeked') clearTimeout(self.seekTimeout); self.seekTimeout = 0; Ox.Log('Video', 'hide loading icon') hideLoadingIcon(); self.$video.show(); self.$playIcon && self.$playIcon.show(); hidePoster(); } function seeking() { Ox.Log('Video', 'XX seeking') if (!self.seekTimeout) { self.seekTimeout = setTimeout(function() { self.$playIcon && self.$playIcon.hide(); Ox.Log('Video', 'XX show') showLoadingIcon(); }, 250); } } function setAudioTrack() { updateVideo(); } function setCensored() { var censored = getCensored(); if (censored != self.censored) { self.censored = censored; censor(); } } function setChapterTitle() { var chapterTitle = getChapterTitle(); if (chapterTitle != self.chapterTitle) { self.chapterTitle = chapterTitle; self.$chapterTitle.html(self.chapterTitle) } } function setMarkers() { //Ox.Log('Video', 'SET MARKERS', self.options.position, self.options['in'], self.options.out, self.$pointMarker); self.$posterMarker && Ox.forEach(self.$posterMarker, function(marker) { 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) { // 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() { that.triggerEvent('setpoint'); } function setPosition(position, from) { self.options.position = Ox.limit(position, self['in'], self.out); /* // disabled self.options.position = Math.round( position * self.options.fps ) / self.options.fps; */ self.options.paused && self.options.showMarkers && setMarkers(); self.options.censored.length && setCensored(); self.options.enableSubtitles && self.$subtitle && setSubtitle(); self.$chapterTitle && setChapterTitle(); self.$position && self.$position.html(formatPosition()); if (self.options.type == 'play') { if (self.loadedMetadata && from != 'video') { self.$video.currentTime(self.options.position); } if (self.iconIsVisible) { self.$playIcon.animate({ opacity: 0 }, 250); self.iconIsVisible = false; } self.$timeline /*&& from != 'timeline'*/ && self.$timeline.options({ position: self.options.position }); } else { //showLoadingIcon(); loadImage(); } } function setResolution() { updateVideo(); that.triggerEvent('resolution', { resolution: self.options.resolution }); } function setSize($element, css, animate, callback) { if ($element) { if (animate) { $element.animate(css, 250, function() { callback && callback(); }); } else { $element.css(css); callback && callback(); } } } 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.videoCSS = getVideoCSS(); self.iconSize = Ox.limit(Math.round(self.height / 10), 16, 32); if (self.$timeline || self.$spaceBottom) { self.timelineWidth = getTimelineWidth(); if (self.$timeline) { self.timelineImageWidth = self.timelineWidth - self.barHeight; } } setSize(that, getCSS('player'), animate, callback); setSize(self.$videoContainer, getCSS('videoContainer'), animate); setSize(self.$video, self.videoCSS, animate); setSize(self.$poster, self.videoCSS, animate); setSize(self.$logo, getCSS('logo'), animate); setSize(self.$loadingIcon, getCSS('loadingIcon'), animate); setSize(self.$playIcon, getCSS('playIcon'), animate); setSize(self.$copyrightIcon, getCSS('copyrightIcon'), animate); setSize(self.$subtitle, getCSS('subtitle'), animate); setSize(self.$controlsTop, getCSS('controlsTop'), animate); setSize(self.$title, getCSS('title'), animate); setSize(self.$chapterTitle, getCSS('title'), animate); setSize(self.$spaceTop, getCSS('spaceTop'), animate); setSize(self.$controlsBottom, getCSS('controlsBottom'), animate); setSize(self.$timeline, getCSS('timeline'), animate, function() { self.$timeline && self.$timeline.options({ width: self.timelineWidth }); }); setSize(self.$spaceBottom, getCSS('spaceBottom'), animate); setSize(self.$find, getCSS('find'), animate, function() { var width = Math.min(128, self.width - 88); // 4 * 16 + 24 self.$findInput.options({ width: width }); self.$findInput.children('input').css({ width: (width - 12) + 'px' }); }); setSize(self.$volume, getCSS('volume'), animate, function() { self.$volumeInput.options({ size: Math.min(128, self.width - 56) }); }); if (self.$posterMarker) { self.posterMarkerCSS = getPosterMarkerCSS(); Ox.forEach(self.$posterMarker, function(marker, position) { setSize(marker, self.posterMarkerCSS[position], animate); }); } } function setSubtitle() { var subtitle = getSubtitle(); if (subtitle != self.subtitle) { self.subtitle = subtitle; setSubtitleText(); } } function setSubtitleText() { self.$subtitle.html( self.subtitle ? Ox.highlight( self.subtitle, self.options.find, 'OxHighlight', true ).replace(/\n/g, '<br/>') : ' <br/> ' // FIXME: weird bug, only in fullscreen, only in chrome ); } function setSubtitlesTrack() { var enableSubtitles = self.options.enableSubtitles && !!self.options.subtitlesTrack; self.options.enableSubtitles = enableSubtitles; setSubtitle(); self.$timeline && self.$timeline.options({ subtitles: getSubtitles() }); if (enableSubtitles && !!self.options.subtitlesTrack) { that.triggerEvent('subtitlestrack', { track: self.options.subtitlesTrack }); } else { that.triggerEvent('subtitles', { subtitles: self.options.enableSubtitles }); } } function setTimelineType() { that.triggerEvent('timeline', {timeline: self.options.timelineType}); } function setVideo() { if (Ox.isObject(self.options.video[0])) { self.audioTracks = Ox.sort(Ox.unique( self.options.video.map(function(video) { return video.track; }) )); if (!Ox.contains(self.audioTracks, self.options.audioTrack)) { self.options.audioTrack = self.audioTracks[0]; } 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 = getVideo(); } else { self.video = [{src: self.options.video}]; self.resolutions = []; self.audioTracks = []; } } function setVolume(volume) { self.options.volume = volume; if (!!self.options.volume == self.options.muted) { toggleMuted(); } else { self.$volumeButton && self.$volumeButton.options({ title: getVolumeImage() }); self.$volumeValue && self.$volumeValue.html( self.options.muted ? 0 : Math.round(self.options.volume * 100) ); } !self.censored && self.$video.volume(self.options.volume); that.triggerEvent('volume', { volume: self.options.volume }); } function showControls() { //Ox.Log('Video', 'showControls'); clearTimeout(self.interfaceTimeout); if (!self.interfaceIsVisible) { self.interfaceIsVisible = true; if (self.$controlsTop) { self.controlsTopAreVisible = true; } if (self.$controlsBottom) { self.controlsBottomAreVisible = true; } self.$controlsTop && self.$controlsTop.animate({ opacity: 1 }, 250); self.$controlsBottom && self.$controlsBottom.animate({ opacity: 1 }, 250); ['find', 'settings', 'volume'].forEach(function(element) { var $element = self['$' + element]; $element && $element.is(':visible') && $element.animate({ opacity: 1 }, 250); }); self.$logo && self.$logo.animate({ top: getCSS('logo').top, opacity: 0.5 }, 250, function() { self.options.logoLink && self.$logo .on({ click: function() { document.location.href = self.options.logoLink; } }); self.options.logoTitle && self.$logo .on({ mouseenter: function(e) { self.$logoTooltip.show(e); }, mouseleave: self.$logoTooltip.hide }); }); self.$subtitle && self.$subtitle.animate({ bottom: getCSS('subtitle').bottom }, 250); } } function showLoadingIcon() { self.$loadingIcon.start().show(); } function showVolume() { if (self.$volume) { !self.interfaceIsVisible && showControls(); self.$volume.is(':hidden') && toggleVolume(); } } function sizechange() { self.videoWidth = self.$video.videoWidth(); self.videoHeight = self.$video.videoHeight(); self.videoCSS = getVideoCSS(); self.$video.css(self.videoCSS); }; function submitFindInput(value, hasPressedEnter) { self.options.find = value; self.results = find(self.options.find); if (self.$find) { self.$results.html(self.results.length); self.$previousResultButton.options({ disabled: !self.results.length }); self.$nextResultButton.options({ disabled: !self.results.length }); self.$clearButton.options({ disabled: !self.options.find }); } self.subtitle && setSubtitleText(); self.$timeline && self.$timeline.options({ find: self.options.find, results: self.results }); if (hasPressedEnter) { if (self.results.length) { goToNext('result', 1); that.gainFocus(); } else { self.$findInput.focusInput(true); } } that.triggerEvent('find', {find: self.options.find}); } function submitPositionInput() { self.$positionInput.hide(); self.$position.html('').show(); setPosition(Ox.parseDuration(self.$positionInput.value())); if (self.playOnSubmit) { togglePaused(); self.$video.play(); self.playOnSubmit = false; } if (self.focus == 'mouseenter' && !self.mouseHasLeft) { that.gainFocus(); } self.mouseHasLeft && hideControls(); that.triggerEvent('position', { position: self.options.position }); that.triggerEvent('submit'); } function toggleFind() { var show = self.$find.is(':hidden'); !show && self.$findInput.blurInput(); self.$find.toggle(); show && self.$findInput.focusInput(false); } function toggleFullscreen(from) { var parentOffset, playOnFullscreen; self.options.fullscreen = !self.options.fullscreen; if (!self.options.paused) { // video won't keep playing accross detach/append self.$video.pause(); playOnFullscreen = true; } if (self.options.fullscreen) { self.$parent = that.parent(); parentOffset = self.$parent.offset(); self.absoluteOffset = that.offset(); self.relativeOffset = { left: self.absoluteOffset.left - parentOffset.left, top: self.absoluteOffset.top - parentOffset.top }; that.detach() .addClass('OxFullscreen') .css({ left: self.absoluteOffset.left + 'px', top: self.absoluteOffset.top + 'px', zIndex: 1000 }) .appendTo(Ox.$body); if (self.options.externalControls) { self.externalControls = true; self.options.externalControls = false; self.$videoContainer.css({top: 0}); } setSizes(true, function() { playOnFullscreen && self.$video.play(); enterFullscreen(); }); } else { // exitFullscreen flag makes the animation end on absolute position self.exitFullscreen = true; that.off('mousemove'); that.find('.OxControls') .trigger('mouseleave') .off('mouseenter mouseleave'); clearTimeout(self.interfaceTimeout); if (self.externalControls) { self.options.externalControls = true; self.$videoContainer.css({top: '16px'}); } setSizes(true, function() { self.exitFullscreen = false; that.detach() .removeClass('OxFullscreen') .css({ left: self.relativeOffset.left + 'px', top: self.relativeOffset.top + 'px', zIndex: 1 }) .appendTo(self.$parent); playOnFullscreen && self.$video.play(); self.options.enableKeyboard && that.gainFocus(); //showControls(); }); } if (self.$fullscreenButton && from != 'button') { self.$fullscreenButton.toggle(); } that.triggerEvent('fullscreen', { fullscreen: self.options.fullscreen }); } function toggleLoop(from) { self.options.loop = !self.options.loop; self.$video.options('loop', self.options.loop); if (self.$loopButton && from != 'button') { self.$loopButton.toggle(); } that.triggerEvent('loop', { loop: self.options.loop }); } function toggleMuted(from) { self.hasVolumeControl && showVolume(); self.options.muted = !self.options.muted; self.$video.muted(self.options.muted); if (!self.options.muted && !self.options.volume) { self.options.volume = 1; self.$video.volume(1); } if (self.$muteButton && from != 'button') { self.$muteButton.toggle(); } self.$volumeButton && self.$volumeButton.options({ title: getVolumeImage() }); self.$volumeInput && self.$volumeInput.value( self.options.muted ? 0 : self.options.volume ); self.$volumeValue && self.$volumeValue.html( self.options.muted ? 0 : Math.round(self.options.volume * 100) ); that.triggerEvent('muted', { muted: self.options.muted }); } function togglePaused(from) { self.options.paused = !self.options.paused; self.$timeline && self.$timeline.options({ paused: self.options.paused }); if (!self.loadedMetadata) { return; } if (self.options.paused) { self.$video.pause(); clearInterval(self.playInterval); if (self.options.showIcon) { togglePlayIcon(); self.$playIcon.animate({ opacity: 1 }, 250); } self.playInToOut = false; } else { hidePoster(); if ( self.options.playInToOut && self.options.position > self.options.out - self.secondsPerFrame ) { setPosition(self.options['in']); } self.$video.play(); self.playInterval = setInterval(playing, self.millisecondsPerFrame); if (self.options.showIcon) { self.$playIcon.animate({ opacity: 0 }, 250, togglePlayIcon); } else if (self.options.showIconOnLoad) { self.$playIcon.animate({ opacity: 0 }, 250); } self.options.showMarkers && hideMarkers(); } if (self.$playButton && from != 'button') { self.$playButton.toggle(); } that.triggerEvent('paused', { paused: self.options.paused }); self.options.paused && that.triggerEvent('position', { position: self.options.position }); } function togglePlayIcon() { self.$playIcon.attr({ src: Ox.UI.getImageURL( 'symbol' + (self.options.paused ? 'Play' : 'Pause' ), 'videoIcon') }); } function toggleScale(from) { self.options.scaleToFill = !self.options.scaleToFill; self.videoCSS = getVideoCSS(); self.$video.animate(self.videoCSS, 250); self.$poster && self.$poster.animate(self.videoCSS, 250); self.$subtitle && self.$subtitle.animate({ bottom: getCSS('subtitle').bottom }, 250); if (self.$scaleButton && from != 'button') { self.$scaleButton.toggle(); } if (self.$posterMarker) { self.posterMarkerCSS = getPosterMarkerCSS(); Ox.forEach(self.$posterMarker, function(marker, position) { marker.animate(self.posterMarkerCSS[position], 250); }); } that.triggerEvent('scale', { scale: self.options.scaleToFill ? 'fill' : 'fit' }); } function toggleSize() { self.options.sizeIsLarge = !self.options.sizeIsLarge; that.triggerEvent('size', { size: self.options.sizeIsLarge ? 'large' : 'small' }); } function toggleVolume() { self.$volume.toggle(); } function updateVideo() { if (!self.options.paused) { self.playOnLoad = true; togglePaused('button'); } self.loadedMetadata = false; showLoadingIcon(); self.video = getVideo(); self.$video.options({ items: self.video }); self.$playButton && self.$playButton.options({disabled: true}); } /*@ changeVolume <f> change volume (num) -> <o> change volume @*/ that.changeVolume = function(num) { changeVolume(num); return that; }; /*@ playInToOut <f> play in to out () -> <o> play in to out @*/ that.playInToOut = function() { playInToOut(); return that; }; /*@ togglePaused <f> toggle loop state () -> <o> toggle loop state @*/ that.toggleLoop = function() { toggleLoop(); return that; }; /*@ togglePaused <f> toggle paused state () -> <o> toggle paused state @*/ that.togglePaused = function() { togglePaused(); return that; }; /*@ toggleMuted <f> toggle muted state () -> <o> toggle muted state @*/ that.toggleMuted = function() { toggleMuted(); return that; }; return that; };