// vim: et:ts=4:sw=4:sts=4:ft=js

/*@
Ox.VideoPlayer <f> Generic Video Player
    (options, self) -> <o> Video Player
    options <o> Options
        annotation <[o]> Array of annotation tracks
            name <s> Name of the annotation track
            data <[o]> Annotation data
                in <n> In point (sec)
                out <n> Out point (sec)
                text <s> Text            
        controls <[[s]]|[[][]]> Controls, first top, then bottom, from left to right
            Can be 'fullscreen', 'scale', 'title', 'find', 'menu',
            'play', 'playInToOut', 'mute', 'volume', 'size', 'timeline', 'space',
            'position', 'resolution', 'settings'. The 'space' control is just
            empty space that separates left-aligned from right-aligned controls.
        duration <n|-1> Duration (sec)
        enableFind <b|false> If true, enable find
        enableFullscreen <b|false> If true, enable fullscreen
        enableKeyboard <b|false> If true, enable keyboard controls
        externalControls <b|false> If true, controls are outside the video
        find <s|''> Query string
        focus <s|'click'> focus on 'click', 'load' or 'mouseover'
        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)
        keepIconVisible <b|false> If true, play icon stays visible after mouseleave
        keepLargeTimelineVisible <b|false> If true, large timeline stays visible after mouseleave
        keepLogoVisible <b|false> If true, logo stays visible after mouseleave
        logo <s|''> Logo image URL
        logoLink <s|''> Logo link URL
        logoTitle <s|''> Text for tooltip
        muted <b|false> If true, video is muted
        paused <b|false> If true, video is paused
        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)
        preload <s|'auto'> 'auto', 'metadata' or 'none'
        out <n|-1> Out point (sec)
        resolution <n|0> resolution
        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
            in <n> In point (sec)
            out <n> Out point (sec)
            text <s> Text
        timeline <s> Timeline image URL
        title <s|''> Video title
        type <s|'play'> 'play', 'in' or 'out'
        video <s|o|''> Video URL
            String or object ({resolution: url, resolution: url, ...})
        volume <n|1> Volume (0-1)
        width <n|256> Width in px
@*/

Ox.VideoPlayer = function(options, self) {

    self = self || {};
    var that = Ox.Element({}, self)
        .defaults({
            annotations: [],
            controlsBottom: [],
            controlsTop: [],
            duration: 0,
            enableFind: false,
            enableFullscreen: false,
            enableKeyboard: false,
            externalControls: false,
            find: '',
            focus: 'click',
            fps: 25,
            fullscreen: false,
            height: 144,
            'in': 0,
            keepIconVisible: false,
            keepLargeTimelineVisible: false,
            keepLogoVisible: false,
            logo: '',
            logoLink: '',
            logoTitle: '',
            muted: false,
            paused: false,
            playInToOut: false,
            position: 0,
            poster: '',
            posterFrame: -1,
            preload: 'auto',
            out: 0,
            resolution: 0,
            scaleToFill: false,
            showControlsOnLoad: false,
            showFind: false,
            showHours: false,
            showIcon: false,
            showIconOnLoad: false,
            showLargeTimeline: false,
            showMarkers: false,
            showMilliseconds: 0,
            showProgress: false,
            subtitles: [],
            timeline: '',
            title: '',
            type: 'play',
            video: '',
            volume: 1,
            width: 256
        })
        .options(options || {})
        .addClass('OxVideoPlayer');

    Ox.UI.$window.bind({
        resize: function() {
            self.options.fullscreen && setSizes();
        }
    });

    if (Ox.isString(self.options.video)) {
        self.video = self.options.video;
    } else {
        self.resolutions = Ox.sort(Object.keys(self.options.video));
        if (!(self.options.resolution in self.options.video)) {
            self.options.resolution = self.resolutions[0];
        }
        self.video = self.options.video[self.options.resolution];
    }

    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;
    }
    self.options.position = Ox.limit(self.options.position, self['in'], self.out);

    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;

    /* 
    ----------------------------------------------------------------------------
    Keyboard
    ----------------------------------------------------------------------------
    */

    if (self.options.enableKeyboard) {
        that.bindEvent({
            key_0: toggleMuted,
            key_1: toggleScale,
            key_equal: function() {
                changeVolumeBy(0.1);
            },
            key_escape: hideControlMenus,
            key_f: focusFind,
            key_g: function() {
                goToNextResult(1);
            },
            key_left: function() {
                setPosition(self.options.position - self.secondsPerFrame);
            },
            key_minus: function() {
                changeVolumeBy(-0.1);
            },
            key_p: function() {
                playInToOut();
            },
            key_right: function() {
                setPosition(self.options.position + self.secondsPerFrame);
            },
            key_shift_f: function() {
                self.options.enableFullscreen && toggleFullscreen();
            },
            key_shift_g: function() {
                goToNextResult(-1);
            },
            key_shift_left: function() {
                setPosition(self.options.position - 1);
            },
            key_shift_right: function() {
                setPosition(self.options.position + 1);
            },
            key_space: togglePaused
        });
        if (self.options.focus == 'mouseenter') {
            that.bind({
                mouseenter: function() {
                    if (!self.inputHasFocus) {
                        that.gainFocus();
                    }
                },
                mouseleave: function() {
                    that.loseFocus();
                }
            });
        } else {
            that.bind({
                click: that.gainFocus
            });
        }
    }

    /* 
    ----------------------------------------------------------------------------
    Mouse
    ----------------------------------------------------------------------------
    */

    if (
        (!self.options.externalControls &&
        (self.options.controlsTop.length || self.options.controlsBottom.length)) ||
        self.options.showIcon
    ) {
        that.bind({
            mouseenter: function() {
                showControls();
                self.mouseHasLeft = false;
                Ox.print('MOUSE HAS ENTERED')
            },
            mouseleave: function() {
                hideControls();
                self.mouseHasLeft = true;
                Ox.print('MOUSE HAS LEFT')
            }
        });
    }

    /* 
    ----------------------------------------------------------------------------
    Video
    ----------------------------------------------------------------------------
    */

    self.$videoContainer = $('<div>')
        .addClass('OxVideoContainer')
        .css({
            top: self.options.externalControls && self.options.controlsTop.length ? '16px' : 0,
        })
        .appendTo(that.$element)

    if (self.options.type == 'play') {

        self.$videoContainer.bind({
            click: function(e) {
                if (!$(e.target).is('.OxLogo')) {
                    togglePaused();
                }
            }
        });

        self.$video = $('<video>')
            .attr(Ox.extend({
                preload: self.options.preload,
                src: self.video
            }, !self.options.paused ? {
                autoplay: 'autoplay'
            } : {}/*, self.options.poster ? {
                poster: self.options.poster
            } : {}*/))
            .css({
                position: 'absolute'
            })
            .bind(Ox.extend({
                ended: ended,
                loadedmetadata: loadedmetadata,
                seeked: seeked,
                seeking: seeking
            }, self.options.progress ? {
                progress: progress
            } : {}))
            .appendTo(self.$videoContainer);

        self.video = self.$video[0];

    } else {

        self.$videoContainer.bind({
            click: function(e) {
                if (!$(e.target).is('.OxLogo')) {
                    goToPoint();
                }
            }
        });

        self.$video = $('<img>')
            .attr({
                src: Ox.UI.PATH + 'png/transparent.png'
            })
            .appendTo(self.$videoContainer);

        loadImage();

    }

    /* 
    ----------------------------------------------------------------------------
    Poster
    ----------------------------------------------------------------------------
    */

    if (self.options.poster) {
        self.$poster = $('<img>')
            .addClass('OxPoster')
            .attr({
                src: self.options.poster
            })
            .appendTo(self.$videoContainer);
        self.posterIsVisible = true;
    }

    /* 
    ----------------------------------------------------------------------------
    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 = $('<img>')
        .addClass('OxLoadingIcon OxVideo')
        .attr({
            src: Ox.UI.getImagePath('symbolLoadingAnimated.svg')
                .replace('/classic/', '/modern/')
        })
        .appendTo(self.$videoContainer);

    if (self.options.showIcon || self.options.showIconOnLoad) {
        self.$playIcon = $('<img>')
            .addClass('OxPlayIcon OxVideo')
            .attr({
                src: Ox.UI.getImagePath(
                    'symbol' + (self.options.paused ? 'Play' : 'Pause') + '.svg'
                ).replace('/classic/', '/modern/')
            })
            .appendTo(self.$videoContainer);
        if (self.options.showIcon) {
            self.$playIcon.addClass('OxInterface');
        }
        if (self.options.showIconOnLoad) {
            self.iconIsVisible = true;
        }
    }

    /* 
    ----------------------------------------------------------------------------
    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.PATH + 'png/videoMarker' + titleCase + '.png'
                    })
                    .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.$element);

            self.options['controls' + titleCase].forEach(function(control) {

                if (control == 'find') {

                    self.$findButton = Ox.Button({
                        style: 'symbol',
                        title: 'find',
                        tooltip: 'Find',
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent({
                        click: toggleFind
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'fullscreen') {

                    self.$fullscreenButton = Ox.Button({
                        style: 'symbol',
                        title: [
                            {id: 'grow', title: 'grow', selected: !self.options.fullscreen},
                            {id: 'shrink', title: 'shrink', selected: self.options.fullscreen}
                        ],
                        tooltip: ['Enter Fullscreen', 'Exit Fullscreen'],
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent({
                        click: function() {
                            toggleFullscreen('button');
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'goto') {

                    self.$setButton = Ox.Button({
                        style: 'symbol',
                        title: 'goTo' + Ox.toTitleCase(self.options.type),
                        tooltip: 'Go to ' + Ox.toTitleCase(self.options.type) + ' Point',
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent({
                        click: goToPoint
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'mute') {

                    self.$muteButton = Ox.Button({
                        style: 'symbol',
                        title: [
                            {id: 'mute', title: 'mute', selected: !self.options.muted},
                            {id: 'unmute', title: 'unmute', selected: self.options.muted}
                        ],
                        tooltip: ['Mute', 'Unmute'],
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent({
                        click: function() {
                            toggleMuted('button');
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'play') {

                    self.$playButton = Ox.Button({
                        style: 'symbol',
                        // FIXME: this is retarded, fix Ox.Button
                        title: [
                            {id: 'play', title: 'play', selected: self.options.paused},
                            {id: 'pause', title: 'pause', selected: !self.options.paused}
                        ],
                        tooltip: ['Play', 'Pause'],
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent({
                        click: function() {
                            togglePaused('button');
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'playInToOut') {

                    self.$playInToOutButton = Ox.Button({
                        style: 'symbol',
                        title: 'playInToOut',
                        tooltip: 'Play In to Out',
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent({
                        click: playInToOut
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'position') {

                    self.positionWidth = 48 +
                        !!self.options.showMilliseconds * 2
                        + self.options.showMilliseconds * 6;

                    self.$position = Ox.Element({
                            tooltip: 'Position'
                        })
                        .addClass('OxPosition')
                        .css({
                            width: (self.positionWidth - 4) + 'px',
                        })
                        .html(formatPosition())
                        .bind({
                            click: function() {
                                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
                                    .options({
                                        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();
                            }
                        })
                        .appendTo(self['$controls' + titleCase].$element);

                    self.$positionInput.children('input').css({
                            width: (self.positionWidth - 6) + 'px',
                            fontSize: '9px'
                        });                

                } else if (control == 'resolution') {

                    self.$resolutionButton = Ox.Element({
                            tooltip: 'Resolution'
                        })
                        .addClass('OxResolutionButton')
                        .html(self.options.resolution + 'p')
                        .bind({
                            click: function() {
                                self.$resolution.toggle();
                            }
                        })
                        .appendTo(self['$controls' + titleCase]);

                    self.$resolution = $('<div>')
                        .addClass('OxResolution')
                        .css({
                            height: (self.resolutions.length * 16) + 'px'
                        })
                        .bind({
                            click: function(e) {
                                var resolution = $(e.target).parent().data('resolution');
                                self.$resolution.hide();
                                if (resolution != self.options.resolution) {
                                    self.$resolution.children().each(function() {
                                        var $this = $(this);
                                        $this.children()[1].src =
                                            $this.data('resolution') == resolution ?
                                            Ox.UI.getImagePath('symbolCheck.svg') :
                                            Ox.UI.PATH + 'png/transparent.png'
                                    });
                                    self.$resolutionButton.html(resolution + 'p');
                                    self.options.resolution = resolution
                                    setResolution();
                                }
                            }
                        })
                        .appendTo(that.$element);
                    self.resolutions.forEach(function(resolution, i) {
                        var $item = $('<div>')
                            .data({
                                resolution: resolution
                            })
                            .bind({
                                mouseenter: function() {
                                    $(this).addClass('OxSelected');
                                },
                                mouseleave: function() {
                                    $(this).removeClass('OxSelected');
                                }
                            })
                            .appendTo(self.$resolution);
                        $('<div>')
                            .html(resolution + 'p')
                            .appendTo($item);
                        $('<img>')
                            .addClass('OxVideo')
                            .attr({
                                src: resolution == self.options.resolution ?
                                    Ox.UI.getImagePath('symbolCheck.svg').replace('/classic/', '/modern/') :
                                    Ox.UI.PATH + 'png/transparent.png'
                            })
                            .appendTo($item);
                    });

                } else if (control == 'scale') {

                    self.$scaleButton = Ox.Button({
                        style: 'symbol',
                        title: [
                            {id: 'fill', title: 'fill', selected: !self.options.scaleToFill},
                            {id: 'fit', title: 'fit', selected: self.options.scaleToFill}
                        ],
                        tooltip: ['Scale to Fill', 'Scale to Fit'],
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent('click', function() {
                        toggleScale('button');
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'set') {

                    self.$setButton = Ox.Button({
                        style: 'symbol',
                        title: 'set' + Ox.toTitleCase(self.options.type),
                        tooltip: 'Set ' + Ox.toTitleCase(self.options.type) + ' Point',
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent({
                        click: setPoint
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'size') {

                    self.$sizeButton = Ox.Button({
                        style: 'symbol',
                        title: [
                            {id: 'grow', title: 'grow', selected: !self.options.sizeIsLarge},
                            {id: 'shrink', title: 'shrink', selected: self.options.sizeIsLarge}
                        ],
                        tooltip: ['Larger', 'Smaller'],
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent('click', toggleSize)
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'space') {

                    self['$space' + titleCase] = $('<div>')
                        .html('&nbsp;') // fixme: ??
                        .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('&nbsp;');
                    }
                    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: 'symbol',
                        title: 'mute',
                        tooltip: 'Volume',
                        type: 'image'
                    })
                    .addClass('OxVideo')
                    .bindEvent({
                        click: toggleVolume
                    })
                    .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.$element);

        self.$results = Ox.Element({
                tooltip: 'Results'
            })
            .addClass('OxResults')
            .html('0')
            .appendTo(self.$find);

        self.$previousButton = Ox.Button({
                disabled: true,
                style: 'symbol',
                title: 'arrowLeft',
                tooltip: 'Previous',
                type: 'image'
            })
            .addClass('OxVideo')
            .bindEvent({
                click: function() {
                    goToNextResult(-1);
                }
            })
            .appendTo(self.$find);

        self.$nextButton = Ox.Button({
                disabled: true,
                style: 'symbol',
                title: 'arrowRight',
                tooltip: 'Next',
                type: 'image'
            })
            .addClass('OxVideo')
            .bindEvent({
                click: function() {
                    goToNextResult(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.$findInput.children('input').css({
            width: (self.positionWidth - 6) + 'px',
        })

        self.$clearButton = Ox.Button({
                disabled: !self.options.find,
                style: 'symbol',
                title: 'delete',
                tooltip: 'Clear',
                type: 'image'
            })
            .addClass('OxVideo')
            .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: 'Hide',
                type: 'image'
            })
            .addClass('OxVideo')
            .bindEvent({
                click: toggleFind
            })
            .appendTo(self.$find);
      
    }

    /* 
    ----------------------------------------------------------------------------
    Volume
    ----------------------------------------------------------------------------
    */

    if (self.options.enableVolume || true) { // fixme

        self.$volume = $('<div>')
            .addClass('OxControls OxVolume')
            .css({
                bottom: self.options.controlsBottom.length ? '16px' : 0
            })
            .appendTo(that.$element);

        self.$hideVolumeButton = Ox.Button({
                style: 'symbol',
                title: 'close',
                tooltip: 'Hide',
                type: 'image'
            })
            .addClass('OxVideo')
            .bindEvent({
                click: toggleVolume
            })
            .appendTo(self.$volume);

        self.$muteButton = Ox.Button({
                style: 'symbol',
                title: [
                    {id: 'mute', title: 'mute', selected: !self.options.muted},
                    {id: 'unmute', title: 'unmute', selected: self.options.muted}
                ],
                tooltip: ['Mute', 'Unmute'],
                type: 'image'
            })
            .addClass('OxVideo')
            .bindEvent({
                click: function() {
                    toggleMuted();
                }
            })
            .appendTo(self.$volume);

        self.$volumeInput = Ox.Range({
                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' && self.options.showMarkers && setMarkers();

    self.results = [];
    if (self.options.subtitles) {
        if (Ox.isArray(self.options.subtitles)) {
            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 = [];
            }
        }
    }

    setSizes();

    function clearInterfaceTimeout() {
        clearTimeout(self.interfaceTimeout);
        self.interfaceTimeout = 0;
    }

    function ended() {
        if (!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;
        }
    }

    function find(query) {
        var results = [];
        if (query.length) {
            query = query.toLowerCase();
            results = Ox.map(self.options.subtitles, function(subtitle) {
                return subtitle.text.toLowerCase().indexOf(query) > -1 ? {
                    'in': subtitle['in'],
                    out: subtitle.out
                } : null;
            });
        }
        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();
            }
        }, 0);
    }

    function formatPosition(position) {
        position = Ox.isUndefined(position) ? self.options.position : position;
        return Ox.formatDuration(position, self.options.showMilliseconds);
    }

    function getCSS(element) {
        var css;
        if (element == 'controlsTop' || element == 'controlsBottom') {
            css = {
                width: self.width + 'px'
            };
        } else if (element == 'find') {
            css = {
                width: Math.min(208, self.width) + 'px'
            };
        } 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') {
            css = {
                bottom: (parseInt(self.height / 16) + !!self.controlsBottomAreVisible * 16) + 'px',
                width: self.width + 'px',
                fontSize: parseInt(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) {
            //Ox.print(e, e.layerX - 56)
            return Ox.limit(
                (e.layerX - 48 - self.barHeight / 2) / self.timelineImageWidth * self.video.duration,
                0, self.video.duration
            );
        } else {
            /*Ox.print(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
            );
        }
    } 

    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() {
        Ox.print('---', self.timelineImageWidth)
        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 = '';
        Ox.forEach(self.options.subtitles, function(v) {
            if (
                v['in'] <= self.options.position &&
                v.out >= self.options.position
            ) {
                subtitle = v.text;
                return false;
            }
        });
        return subtitle;
    }

    function getTimeline() {
        var $timeline = Ox.SmallVideoTimeline({
                _offset: getTimelineLeft(),
                duration: self.options.duration,
                find: self.options.find,
                'in': self.options['in'],
                out: self.options.out,
                paused: self.options.paused,
                results: self.results,
                showMilliseconds: self.options.showMilliseconds,
                subtitles: self.options.subtitles,
                timeline: self.options.timeline,
                type: 'player',
                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: '-webkit-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'
            })
            .bindEvent({
                position: function(data) {
                    setPosition(data.position, 'timeline');
                }
            });
        //Ox.print('??', $timeline.find('.OxInterface'))
        $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;
            }
            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 :
                    curr == 'position' ? self.positionWidth :
                    curr == 'resolution' ? 36 : 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 == 'space' ? 0 : 16
                );
            }, 0);
    }

    function getVideoCSS() {
        var playerWidth = self.width,
            playerHeight = self.height,
            playerRatio = playerWidth / playerHeight,
            videoWidth = self.videoWidth,
            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: parseInt((playerWidth - width) / 2),
            top: parseInt((playerHeight - height) / 2),
            width: width,
            height: height
        };
    }

    function getVolumeImageURL() {
        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 Ox.UI.getImagePath('symbol' + symbol + '.svg').replace('/classic/', '/modern/');
    }

    function goToPoint() {
        that.triggerEvent('gotopoint');
    }

    function hideControlMenus() {
        ['find', 'volume', 'resolution'].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.print('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.unbind('click');
                    self.options.logoTitle &&
                        self.$logo.unbind('mouseenter').unbind('mouseleave');
                });
                self.$subtitle && self.$subtitle.animate({
                    bottom: getCSS('subtitle').bottom,
                }, 250);
            }
        }, self.options.fullscreen ? 2500 : 1000);
    }

    function hideLoadingIcon() {
        self.$loadingIcon.hide().attr({
            src: Ox.UI.getImagePath('symbolLoading.svg')
                .replace('/classic/', '/modern/')
        });
    }

    function hideMarkers() {
        Ox.forEach(self.$posterMarker, function(marker) {
            marker.hide();
        });
        Ox.forEach(self.$pointMarker, function(markers) {
            Ox.forEach(markers, function(marker) {
                marker.hide();
            });
        });
    }

    function isEqual(a, b) {
        return Math.abs(a - b) < 0.001;
    }

    function loadImage() {
        self.$video
            .one({
                load: function() {
                    Ox.print('IMAGE LOADED', self.options.video(self.options.position, self.options.width))
                    hideLoadingIcon();
                }
            })
            .attr({
                src: self.options.video(self.options.position, self.options.width)
            });
    }

    function loadedmetadata() {

        Ox.print('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.options.out < self.video.duration ?
            self.options.out : self.video.duration;
        self.options.duration = self.out - self['in'];
        self.video.currentTime = self.options.position;

        self.options.paused && self.options.showMarkers && setMarkers();
        self.options.paused && self.playOnLoad && togglePaused('button');
        self.$playButton && self.$playButton.options({
            disabled: false
        });

        hideLoadingIcon();
        if (self.options.showIcon || self.options.showIconOnLoad) {
            //!self.options.keepIconVisible && self.$playIcon.addClass('OxInterface');
            if (self.options.showIconOnLoad) {
                self.$playIcon.animate({
                    opacity: 1
                }, 250);
            }
        }

        !hadDuration && self.$timeline && self.$timeline.replaceWith(
            self.$timeline = getTimeline()
        );

        if (self.options.enableKeyboard && self.options.focus == 'load') {
            that.gainFocus();
        }

    }

    function loadedsubtitles() {
        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
                });
            }
        }
    }

    function parsePositionInput(str) {
        var split = str.split(':').reverse();
        while (split.length > 3) {
            split.pop();
        }
        return split.reduce(function(prev, curr, i) {
            return prev + (parseFloat(curr) || 0) * Math.pow(60, i);
        }, 0);
    }

    function playing() {
        self.options.position = self.video.currentTime;
        if (
            (self.options.playInToOut || self.playInToOut) &&
            self.options.position >= self.options.out
        )  {
            togglePaused();
            setPosition(self.options.out/*, 'video'*/);
            //ended();
            self.playInToOut = false;
        } else {
            setPosition(self.options.position, 'video');
        }
        that.triggerEvent('position', {
            position: self.options.position
        });
    }

    function playInToOut() {
        if (self.options.out > self.options['in']) {
            Ox.print('inToOut', 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 seeked() {
        Ox.print('XX seeked')
        clearTimeout(self.seekTimeout);
        self.seekTimeout = 0;
        Ox.print('XX hide')
        hideLoadingIcon();
        self.$playIcon && self.$playIcon.show();
    }

    function seeking() {
        Ox.print('XX seeking')
        if (!self.seekTimeout) {
            self.seekTimeout = setTimeout(function() {
                self.$playIcon && self.$playIcon.hide();
                Ox.print('XX show')
                showLoadingIcon();
            }, 250);
        }
    }

    function setMarkers() {
        //Ox.print('SET MARKERS', self.options.position, self.options['in'], self.options.out, self.$pointMarker);
        Ox.forEach(self.$posterMarker, function(marker) {
            isEqual(self.options.position, self.options.posterFrame) ?
                marker.show() : marker.hide();
        });
        Ox.forEach(self.$pointMarker, function(markers, point) {
            Ox.forEach(markers, function(marker) {
                //Ox.print(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();
            });
        });
    }

    function setPoint() {
        that.triggerEvent('setpoint');
    }

    function setPosition(position, from) {
        self.options.position = Ox.limit(position, self['in'], self['out']);
        /*
        self.options.position = Math.round(
            position * self.options.fps
        ) / self.options.fps;
        */
        self.options.paused && self.options.showMarkers && setMarkers();
        self.$subtitle && setSubtitle();
        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;
            }
            if (self.posterIsVisible) {
                self.$poster.animate({
                    opacity: 0
                }, 250);
                self.posterIsVisible = false;
            }
            self.$timeline /*&& from != 'timeline'*/ && self.$timeline.options({
                position: self.options.position
            });
        } else {
            //showLoadingIcon();
            loadImage();
        }
    }

    function setResolution() {
        if (!self.options.paused) {
            self.playOnLoad = true;
            togglePaused('button');
        }
        self.loadedMetadata = false;
        showLoadingIcon();
        self.video.src = self.options.video[self.options.resolution];
        self.$playButton && self.$playButton.options({
            disabled: true
        });
    }

    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(callback) {
        var 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 = Math.max(Math.round(self.height / 10), 16);
        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.$subtitle, getCSS('subtitle'), animate);
        setSize(self.$controlsTop, getCSS('controlsTop'), animate);
        setSize(self.$title, getCSS('title'), animate);
        setSize(self.$spaceTop, getCSS('spaceTop'), animate);
        setSize(self.$controlsBottom, getCSS('controlsBottom'), animate);
        setSize(self.$timeline, getCSS('timeline'), animate, function() {
            self.$timeline.options({
                width: self.timelineWidth
            });
        });
        setSize(self.$spaceBottom, getCSS('spaceBottom'), animate);
        setSize(self.$find, getCSS('find'), animate, function() {
            self.$findInput.options({
                width: Math.min(128, self.width - 80)
            });
        });
        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() {
        Ox.print('setSubTx', self.subtitle, self.options.find)
        self.$subtitle.html(
            self.subtitle ? 
            Ox.highlight(self.subtitle, self.options.find, 'OxHighlight')
                .replace(/\n/g, '<br/>') : '&nbsp;<br/>&nbsp;'
                // FIXME: weird bug, only in fullscreen, only in chrome
        );
        Ox.print('?!?', self.$subtitle.css('bottom'), self.$subtitle.height())
    }

    function changeVolumeBy(num) {
        showVolume();
        self.options.volume = Ox.limit(self.options.volume + num, 0, 1);
        setVolume(self.options.volume);
        self.$volumeInput && self.$volumeInput.options({
            value: self.options.volume
        });
    }

    function setVolume(volume) {
        self.options.volume = volume;
        if (!!self.options.volume == self.options.muted) {
            self.options.muted = !self.options.muted;
            self.video.muted = self.options.muted;
            self.$muteButton.toggleTitle();
        }
        self.video.volume = self.options.volume;
        self.$volumeButton.attr({
            src: getVolumeImageURL()
        });
        self.$volumeValue.html(self.options.muted ? 0 : Math.round(self.options.volume * 100));
    }

    function showControls() {
        Ox.print('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);
            self.$find && self.$find.is(':visible') && self.$find.animate({
                opacity: 1
            }, 250);
            self.$volume && self.$volume.is(':visible') && self.$volume.animate({
                opacity: 1
            }, 250);
            self.$resolution && self.$resolution.is(':visible') && self.$resolution.animate({
                opacity: 1
            }, 250);
            self.$logo && self.$logo.animate({
                top: getCSS('logo').top,
                opacity: 0.5
            }, 250, function() {
                self.options.logoLink && self.$logo
                    .bind({
                        click: function() {
                            document.location.href = self.options.logoLink;
                        }
                    });
                self.options.logoTitle && self.$logo
                    .bind({
                        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.attr({
            src: Ox.UI.getImagePath('symbolLoadingAnimated.svg')
                .replace('/classic/', '/modern/')
        }).show();
    }

    function showVolume() {
        !self.interfaceIsVisible && showControls();
        self.$volume && self.$volume.is(':hidden') && toggleVolume();
    }

    function submitFindInput(value, hasPressedEnter) {
        Ox.print('submitFindInput', value, hasPressedEnter)
        self.options.find = value;
        self.results = find(self.options.find);
        Ox.print('results', self.results.length);
        if (self.$find) {
            self.$results.html(self.results.length);
            self.$previousButton.options({
                disabled: self.results.length <= 1
            });
            self.$nextButton.options({
                disabled: self.results.length <= 1
            });
            self.$clearButton.options({
                disabled: !self.options.find
            });
        }
        self.subtitle && setSubtitleText();
        self.$timeline && self.$timeline.options({
            find: self.options.find,
            results: self.results
        });
        if (hasPressedEnter) {
            self.results.length ? goToNextResult(1) : self.$findInput.focusInput();
        }
    }

    function goToNextResult(direction) {
        var found = false,
            position = 0;
        direction == -1 && self.results.reverse();
        Ox.forEach(self.results, function(v) {
            if (direction == 1 ? v['in'] > self.options.position : v['out'] < self.options.position) {
                position = v['in'];
                found = true;
                return false;
            }
        });
        direction == -1 && self.results.reverse();
        if (!found) {
            position = self.results[direction == 1 ? 0 : self.results.length - 1]['in'];
        }
        setPosition(position + self.secondsPerFrame);
    }

    function submitPositionInput() {
        self.$positionInput.hide();
        self.$position.html('').show();
        Ox.print('###', parsePositionInput(self.$positionInput.options('value')))
        setPosition(parsePositionInput(self.$positionInput.options('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
        });
    }

    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()
                .css({
                    left: self.absoluteOffset.left + 'px',
                    top: self.absoluteOffset.top + 'px',
                    zIndex: 1000
                })
                .appendTo(Ox.UI.$body);
            setSizes(function() {
                playOnFullscreen && self.video.play();
                that.bind({
                    mousemove: function() {
                        showControls();
                        hideControls();                            
                    }
                });
                that.find('.OxControls').bind({
                    mouseenter: function() {
                        self.mouseIsInControls = true;
                    },
                    mouseleave: function() {
                        self.mouseIsInControls = false;
                    }
                });
                showControls();
                hideControls();
                that.gainFocus();
            });
        } else {
            // flag makes the animation end on absolute position
            self.exitFullscreen = true;
            that.unbind('mousemove');
            that.find('.OxControls')
                .trigger('mouseleave')
                .unbind('mouseenter')
                .unbind('mouseleave');
            clearTimeout(self.interfaceTimeout);
            setSizes(function() {
                self.exitFullscreen = false;
                that.detach()
                    .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.toggleTitle();
        }
    }

    function toggleMuted(from) {
        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.toggleTitle();
        }
        self.$volumeButton && self.$volumeButton.attr({
            src: getVolumeImageURL()
        });
        self.$volumeInput && self.$volumeInput.options({
            value: self.options.muted ? 0 : self.options.volume
        });
        self.$volumeValue && self.$volumeValue.html(
            self.options.muted ? 0 : Math.round(self.options.volume * 100)
        );
    }

    function togglePaused(from) {
        self.options.paused = !self.options.paused;
        self.$timeline && self.$timeline.options({
            paused: self.options.paused
        });
        if (self.options.paused) {
            self.video.pause();
            clearInterval(self.playInterval);
            if (self.options.showIcon) {
                togglePlayIcon();
                self.options.showIcon && self.$playIcon.animate({
                    opacity: 1
                }, 250);
            }
        } else {
            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.options.showIcon && self.$playIcon.animate({
                    opacity: 0
                }, 250, togglePlayIcon);
            }
            self.options.showMarkers && hideMarkers();
        }
        if (self.$playButton && from != 'button') {
            self.$playButton.toggleTitle();
        }
    }

    function togglePlayIcon() {
        self.$playIcon.attr({
            src: Ox.UI.getImagePath(
                'symbol' + (self.options.paused ? 'Play' : 'Pause'
            )+ '.svg').replace('/classic/', '/modern/')
        });
    }

    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);
        if (self.$scaleButton && from != 'button') {
            self.$scaleButton.toggleTitle();
        }
        if (self.$posterMarker) {
            self.posterMarkerCSS = getPosterMarkerCSS();
            Ox.forEach(self.$posterMarker, function(marker, position) {
                marker.animate(self.posterMarkerCSS[position], 250);
            });
        }
    }

    function toggleSize() {
        self.options.sizeIsLarge = !self.options.sizeIsLarge;
        that.triggerEvent('size', {
            size: self.options.sizeIsLarge ? 'large' : 'small'
        });
    }

    function toggleVolume() {
        self.$volume.toggle();
    }

    self.setOption = function(key, value) {
        if (key == 'fullscreen') {
            toggleFullscreen();
        } else if (key == 'height' || key == 'width') {
            setSizes();
        } else if (key == 'in' || key == 'out') {
            self.options.paused && setMarkers();
        } else if (key == 'muted') {
            toggleMuted();
        } else if (key == 'paused') {
            togglePaused();
        } else if (key == 'position') {
            setPosition(value);
        } else if (key == 'posterFrame') {
            self.options.paused && setMarkers();
        } else if (key == 'scaleToFill') {
            toggleScale();
        }
    };

    that.playInToOut = function() {
        playInToOut();
        return that;
    };

    that.togglePaused = function() {
        togglePaused();
        return that;
    }

    that.toggleMuted = function() {
        toggleMuted();
        return that;
    }

    return that;

};
