OSMF HLS Plugin

Playback Apple's HLS VOD, Live and DVR streams in OSMF based players.

Grind Player Documentation  

OSMF + Flex based flash video player, which provides most needed functionality in nowadays.

About

Grind Player — is a OSMF + Flex based flash video player, which provides most needed functionality in nowadays.

Download Grind Player or visit Grind Player on GitHub

Features

  • Quality switcher
  • Alternate audio track switcher
  • Built-in scrubbar thumbnails
  • Subtitles
  • Advertisement
  • Localization
  • Settings remember (volume, quality)
  • Statistics info

Difference between Grind and SMP:

  • Do 5 retries on ioerror while http streaming download f4f-fragments and f4m-manifest before halt
  • Can properly download fragments with response header Transfer-Encoding: chunked

Shortcuts

KeyDescription
F or Double click / ESC or Double clickIn / out fullscreen mode
LToggle locale
SToggle scaleMode
T or Right click → “Show statistics”Show statistics info
Space or ClickToggle play / pause
 Change volume by 10%
 Seek by 10 seconds
Shift ← Shift →Seek by 5 seconds
Ctrl ← Ctrl →Seek by 1 minute
=  0 or NUM+ NUM– NUM0Zoom video in / out / reset, by 1%
Most of shortcuts will work only in window mode.

Embed

<!DOCTYPE html>
<html>
<head>
    <title>Grind Player</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="http://yandex.st/swfobject/2.2/swfobject.min.js"></script>
    <script type="text/javascript">
        var flashvars = {
            src: "YOUR SOURCE URL HERE"
        };
        var params = {
            allowFullScreen: true
            , allowScriptAccess: "always"
            , bgcolor: "#000000"
        };
        var attrs = {
            name: "player"
        };

        swfobject.embedSWF("GrindPlayer.swf", "player", "854", "480", "10.2", null, flashvars, params, attrs);
    </script>
</head>
<body>
    <div id="player"></div>
</body>
</html>

Flashvars

ParameterTypeDefaultDescription
autoPlayBooleantrueStarts playing the media automagically, when ready
autoRewindBooleantrueReturn meida to the beginning after playback completes.
bufferTimeNumber4.0Set buffer time
clipEndTimeNumberSpecifies an offset in seconds from the beginning of the content stream. When specified, the stream is presented as a subclip, with playback beginning at the given start time. The default value is NaN, which starts playback at the actual beginning of the stream. Progressive content is unaffected by this setting.
clipStartTimeNumberSpecifies an offset in seconds from the beginning of the content stream. When specified, the stream is presented as a subclip, with playback beginning at the given start time. The default value is NaN, which starts playback at the actual beginning of the stream. Progressive content is unaffected by this setting.
configurationStringURL to configuration XML file, use it instead flashvars
controlBarAutoHideBooleantrueAuto-hide control bar
controlBarAutoHideTimeoutNumber3Auto-hide timeout, in seconds
controlBarFullScreenAutoHideBooleantrueAuto-hide control bar in full screen mode
enableStageVideoBooleantrueEnable to use StageVideo
haltOnErrorBooleanfalsePlayer halts if there is a problem loading a plug-in
javascriptCallbackFunctionStringJavaScript function which will be called on some player events
loopBooleanfalseRestarts media playback when the end of the file is reached
mutedBooleanfalseSpecifies whether the player initially loads content with its volume on or off, if not auto-remembered before
namespace_*StringDefine namespaces for load plugin
plugin_*StringLoad custom plugin plugin_m3u8: "http://example.com/plugin.swf"
scaleModeStringletterbox[ stretch | zoom | none ]
srcStringSource URL
src_defaultAudioLabelStringDefaultLabel for default audio stream, only for streams with alternate audio tracks
streamTypeStringliveOrRecorded[ recorded | live | dvr ], always set live or dvr for hls stream
tintColorString#B91F1FTheme color
volumeNumber1.0Default volume value on start, if not auto-remembered before

JavaScript API

JavaScript API allow us to communicate with player from javascript.

First of all we need to get reference to our player, when it ready:

var flashvars = {
    ...
    , javascriptCallbackFunction: "onJSBridge"
}

var player;
function onJSBridge(playerId, event, data) {
    switch(event) {
        case "onJavaScriptBridgeCreated":
            // reference to player
            player = document.getElementById(playerId);
            break;

        // player state change
        case "ready":
        case "loading":
        case "playing":
        case "paused":
        case "buffering":

        // other events
        case "mediaSize":
        case "seeking":
        case "seeked":
        case "volumeChange":
        case "durationChange":
        case "timeChange":
        case "progress": // for progressive download only
        case "complete":
        case "advertisement":

        default:
            // console.log(event, data);
            break;
    }
}

Now with reference to the player, we can call methods in player, or add event listeners.

List of all available methods:

Method nameDescription
addEventListener(eventName:String, callbackFunctionName:String)
addEventListeners(map:Object)map is a key-value object, where key is eventName, and value is a callbackFunctionName
authenticatefor adobe drm
authenticateWithTokenfor adobe drm
canSeekTo(time:Number):Boolean
closeAd(id:String)see Advertisement section
displayAd(data:Object)see Advertisement section
getAlternativeAudioItemAt(index:int):Objectreturn {streamName:String, info:Object}
getAlternativeAudioStreamSwitching():Boolean
getAudioPan():Number
getAutoDynamicStreamSwitch():Boolean
getAutoPlay():Boolean
getAutoRewind():Boolean
getBitrateForDynamicStreamIndex(index:int):Number
getBuffering():Boolean
getBufferLength():Number
getBufferTime():Number
getBytesLoaded
getBytesLoadedUpdateInterval
getBytesTotal
getCanBuffer():Boolean
getCanLoad():Boolean
getCanPause():Boolean
getCanPlay():Boolean
getCanSeek():Boolean
getCurrentAlternativeAudioStreamIndex():int
getCurrentDynamicStreamIndex():int
getCurrentSubtitlesStreamIndex():int
getCurrentTime():Number
getCurrentTimeUpdateInterval():Number
getDrmEndDatefor adobe drm
getDrmPeriodfor adobe drm
getDrmStartDatefor adobe drm
getDrmStatefor adobe drm
getDuration():Number
getDynamicStreamItemAt(index:uint):Objectreturn {streamName, bitrate, width, height}
getDynamicStreamSwitching():Boolean
getHasAlternativeAudio():Boolean
getHasAudio():Boolean
getHasDRMfor adobe drm
getHasSubtitles():Boolean
getIsDVRRecording():Boolean
getIsDynamicStream():Boolean
getLoop():Boolean
getMaxAllowedDynamicStreamIndex():int
getMediaHeight():Number
getMediaWidth():Number
getMuted():Boolean
getNumAlternativeAudioStreams():int
getNumDynamicStreams():int
getNumSubtitlesStreams():int
getPaused():Boolean
getPlaying():Boolean
getScaleMode():String
getSeeking():Boolean
getState():String
getStreamType():String
getSubtitlesItemAt(index:int):Objectreturn {src, label, language}
getTemporal():Boolean
getVolume():Number0.0 … 1.0
load()Load new media resource
pause()
play2()“play2” because javascript name conflict
seek(time:Number)Seek to specific time
setAudioPan(value:Number)-1.0 … 1.0
setAutoDynamicStreamSwitch(value:Boolean)Auto switch stream video quality
setAutoPlay(value:Boolean)
setAutoRewind(value:Boolean)
setBufferTime(value:Number)
setBytesLoadedUpdateInterval(value:Number=250)
setCurrentTimeUpdateInterval(value:Number=250)How often player will update current time, in milliseconds
setLoop(value:Boolean)
setMaxAllowedDynamicStreamIndex(value:Number)
setMediaResourceURL(url:String)Set new source url and load it
setMuted(value:Boolean)
setResourceMetadata(namespace:String, value:Object)New values of resource metadata will apply on next load()
setScaleMode(value:String)[ letterbox | stretch | zoom | none ]
setVolume(value:Number)0.0 … 1.0
stop2()“stop2” because javascript name conflict
switchAlternativeAudioIndex(index:int)-1 is default audio stream (embeded in video stream)
switchDynamicStreamIndex(index:int)
switchSubtitlesIndex(index:int)-1 is off

Use it like this:

player.setScaleMode("stretch");

List of available events:

Event nameDescription
audioSwitchingChange
autoSwitchChange
bufferingChange
bufferTimeChange
bytesLoadedChangeonly for progressive download
bytesTotalChangeonly for progressive download
canBufferChange
canPauseChange
canPlayChange
canSeekChange
completeEnd of video playback
currentTimeChange
displayObjectChange
drmStateChangefor adobe drm
durationChange
hasAlternativeAudioChange
hasAudioChange
hasDisplayObjectChange
hasSubtitlesChange
isDynamicStreamChange
isRecordingChangefor dvr
loadStateChange
mediaErrorHandle this event if you want custom behavior on playback error
mediaPlayerStateChange
mediaSizeChange
mutedChange
numAlternativeAudioStreamsChange
numDynamicStreamsChange
numSubtitlesStreamsChange
onMetaDataNetStream onMetaData info
panChange
playStateChange
qosUpdateQuality of Service info
seekingChange
subtitlesSwitchingChange
switchingChange
temporalChange
volumeChange

Use it like this:

player.addEventListener("mediaError", "onMediaError");
...
function onMediaError() {
    console.log(arguments.callee.name, arguments);
    // start media again
    // player.load();
}

Subtitles

Note: Subtitles make sence only for VOD.

Subtitles plugin support SubRip file format, it looks like this:

1
00:00:12,000 --> 00:00:14,000
Good morning

2
00:00:17,000 --> 00:00:19,000
Thanks for coming

...

Add subtitles to player via flashvars:

var subtitles = JSON.stringify({
    subtitles: [
        {
              src: "subtitles_en.srt"   // url to .srt file
            , label: "English"          // label for subtitles, will show in menu
            , language:"en"             // optional
        }
        , {
            ...
        }
    ]

    // optinal, configuration to display subtitles
    // all properties by default, you can change only what you need
    , config: {
        // fontSize, in percentage of available width
        // for GrindPlayer available width for subtitles is 70% of player width
        // ex: if player width is 1280px, then real fontSize will be
        // Math.floor(1280 * 0.7 * 0.035) = 31
          fontSize: 0.035

        , minFontSize: 14
        , maxFontSize: 36
        , textColor: 0xDFDFDF
        , bgColor: 0x101010
        , bgAlpha: 0.8
    }
});
var flashvars = {
    ...
    , "src_http://kutu.ru/osmf/plugins/subtitles": encodeURIComponent(subtitles)
};

via media resource metadata in already loaded player:

var subtitles = JSON.stringify(
    subtitles: [
        {
            src: "subtitles_en.srt" // url so .srt file
            , label: "English"      // label for subtitles, will show in menu
            , language:"en"         // optional
        }
        , {
            ...
        }
    ]
    , config: {
        ...
    }
);
player.setResourceMetadata("http://kutu.ru/osmf/plugins/subtitles", subtitles);
player.load();

To remove subtitles in already loaded player:

player.setResourceMetadata("http://kutu.ru/osmf/plugins/subtitles");
player.load();

Advertisement

Advertisement plugin have two options to use:

Option 1 (simple, via flashvars)

You have very strict possibility to show ads. You can show only preroll, midroll and postroll.

Preroll:

var flashvars = {
    ...
    src_preroll: "URL TO VIDEO"
};

Midroll:

var flashvars = {
    ...
    src_midroll: "URL TO VIDEO"
    src_midrollTime: 30 // time in the main media when the ads should be shown
};

Postroll:

var flashvars = {
    ...
    src_postroll: "URL TO VIDEO"
};

Option 2 (advanced, via javascript)

You have very flexible possibilities to show ads, but it require write some JavaScript code.

There are two methods, that can display and close ads:

  • displayAd(data:Object)
  • closeAd(id:String)
player.displayAd({
    // optional, but highly recommend to fill
    id:String

    // url to media (video, image, swf, audio)
    , url:String

    // if your image url doesn't have an extension
    // then use this mediaType: "image"
    , mediaType:String // video | audio | image | swf

    // if your image doesn't have jpg or png extension
    // then use this mimeType: "image/jpeg"
    , mimeType:String

    // default: true
    , smoothing:Boolean

    // if false, control bar in pause state and buffering animation will not hide
    // e.g.: this parameter should be false for logo case
    // default: true
    , isAdvertisement:Boolean

    // self-explained
    , hideScrubBarWhilePlayingAd:Boolean
    , pauseMainMediaWhilePlayingAd:Boolean
    , resumePlaybackAfterAd:Boolean // using only with previous parameter
    , pauseMainMediaOnClick:Boolean // using only with "clickUrl"

    // pre buffer media before start to show (if media is bufferable)
    // default: false
    , preBufferAd:Boolean

    // layout info, all paramters is optinal, see examples below
    , layoutInfo: {
        index:Number
        , scaleMode:String          // [ letterbox | stretch | zoom | none ]
        , horizontalAlign:String    // [ left | center | right ]
        , verticalAlign:String      // [ top | middle | bottom ]
        , x:Number
        , y:Number
        , width:Number
        , height:Number
        , percentX:Number
        , percentY:Number
        , percentWidth:Number
        , percentHeight:Number
        , left:Number
        , right:Number
        , top:Number
        , bottom:Number
    }

    // navigate user to this url on click
    , clickUrl:String

    // show "x" button at top-right corner
    , closable:Boolean

    // auto close ad after n seconds
    , autoCloseAfter:Number // usable for images or swf ads

    // callback funtion names
    , onStart:String
    , onComplete:String
    , onClose:String
    , onClick:String
});

player.closeAd(id);

You can handle what ad ids currently showing:

function onJSBridge(playerId, event, data) {
    switch(event) {
        ...
        case "advertisement":
            // data is an array with currently showing ads
            console.log(event, data);
            break;
    }
}

Examples:

Show preroll video:

var player;
var prerollShown = false;

function onJSBridge(playerId, event, data) {
    switch(event) {
        case "onJavaScriptBridgeCreated":
            player = document.getElementById(playerId);
            player.addEventListener("bufferingChange", "onBufferingChange");
            break;
    }
}

function onBufferingChange(buffering) {
    if (buffering && !prerollShown) {
        prerollShown = true;
        player.displayAd({
            id: "showVideoPrerollAd"
            , url: "ad.mp4"
            , hideScrubBarWhilePlayingAd: true
            , pauseMainMediaWhilePlayingAd: true
            , resumePlaybackAfterAd: true
            , clickUrl: "http://osmfhls.kutu.ru"
            , closable: true
        });
    }
}

Show midroll video at 10 seconds of main video:

var player;
var midrollShown = false;

function onJSBridge(playerId, event, data) {
    switch(event) {
        case "onJavaScriptBridgeCreated":
            player = document.getElementById(playerId);
            break;
        ...
        case "timeChange":
            if (!midrollShown && data.currentTime >= 10) {
                midrollShown = true;
                player.displayAd({
                    id: "showVideoMidrollAd"
                    , url: "ad.mp4"
                    , hideScrubBarWhilePlayingAd: true
                    , pauseMainMediaWhilePlayingAd: true
                    , resumePlaybackAfterAd: true
                    , preBufferAd: true
                    , clickUrl: "http://osmfhls.kutu.ru"
                    , closable: true
                });
            }
            break;
    }
}

Show postroll video:

var player;
var postrollShown = false;

function onJSBridge(playerId, event, data) {
    switch(event) {
        case "onJavaScriptBridgeCreated":
            player = document.getElementById(playerId);
            break;
        ...
        case "complete":
            if (!postrollShown) {
                postrollShown = true;
                player.displayAd({
                    id: "showVideoPostrollAd"
                    , url: "ad.mp4"
                    , hideScrubBarWhilePlayingAd: true
                    , clickUrl: "http://osmfhls.kutu.ru"
                    , closable: true
                });
            }
            break;
    }
}

Show overlay video at center-bottom and handle events:

player.displayAd({
    id: "showOverlayVideoAdCenterBottom"
    , url: "ad.mp4"
    , preBufferAd: true
    , layoutInfo: {
        scaleMode: "letterbox"
        , percentWidth: 20
        , percentHeight: 20
        , percentX: 40 // (100% - percentWidth) / 2 = 40
        , bottom: 50
    }
    , closable: true
    , clickUrl: "http://osmfhls.kutu.ru"
    , onStart: "onAdStart"
    , onComplete: "onAdComplete"
    , onClose: "onAdClose"
    , onClick: "onAdClick"
});
function onAdStart(id) {
    console.log(arguments.callee.name, id);
}
function onAdComplete(id) {
    console.log(arguments.callee.name, id);
}
function onAdClose(id) {
    console.log(arguments.callee.name, id);
}
function onAdClick(id) {
    console.log(arguments.callee.name, id);
}

Show SWF ad at center-bottom:

player.displayAd({
    id: "showSwfAdCenterBottom"
    , url: "banner_468_60.swf"
    , layoutInfo: {
        width: 468
        , height: 60
        , horizontalAlign: "center"
        , bottom: 50
    }
    , clickUrl: "http://osmfhls.kutu.ru"
    , pauseMainMediaOnClick: true
    , closable: true
});

Show image, it can be jpg, png or gif (not animated) at center-bottom:

player.displayAd({
    id: "showImageAdCenterBottom"
    , url: "banner_468_60.png"
    , layoutInfo: {
        width: 468
        , height: 60
        , horizontalAlign: "center"
        , bottom: 50
    }
    , clickUrl: "http://osmfhls.kutu.ru"
    , pauseMainMediaOnClick: true
    , closable: true
});

It is not only about ads, you can even just show clickable logo, with respect width/height of video player at right-bottom corner:

player.displayAd({
    id: "logo"
    , url: "logo.png"
    , isAdvertisement: false
    , layoutInfo: {
        scaleMode: "letterbox"
        , percentWidth: 10
        , percentHeight: 10
        , right: 10
        , bottom: 30
    }
    , clickUrl: "http://osmfhls.kutu.ru"
});

Thumbnails

Thumbnails - is a sequence of little images, captured every n seconds from original video, and shown when user hover mouse over scrubbar.

Note: Thumbnails make sence only for VOD.

Requirements:

  • FFMpeg
  • ImageMagick.
    Note: ImageMagick has his own ffmpeg binary, and after install ImageMagick check your PATH, and place original ffmpeg path before ImageMagick, or use absolute path to ffmpeg in your scripts
  • Respect resolution image limits.

    ... the maximum size for a BitmapData object is 8,191 pixels in width or height, and the total number of pixels cannot exceed 16,777,215 pixels. (So, if a BitmapData object is 8,191 pixels wide, it can only be 2,048 pixels high.)

    ActionScript 3 Documentation

  • Recommended thumbnails image size less then 2mb.

Step 1.1

If you have original video file, create batch file ffmpeg-create-thumbnails.bat with:

@echo off
@set out_dir=%~2
@set thumb_every_seconds=10
@set height=72

ffmpeg.exe -i "%~1" -vf fps=fps=1/%thumb_every_seconds%,scale=iw/ih*%height%:%height% "%out_dir%%%06d.png"

and use it like this:

ffmpeg-create-thumbnails.bat video.mp4 thumbnails-tmp-dir\

It will create sequence of images, captured from video.mp4 every 10 seconds with height 72px (with respect aspect ratio) and placed in thumbnails-tmp-dir temporary directory.

Step 1.2

If you don’t have original video, instead you only have ts-chunks, then create ffmpeg-create-thumbnails.bat with:

@echo off
@set out_dir=%~2
@set thumb_every_seconds=10
@set height=72

@setlocal enabledelayedexpansion

:: create list=concat:file|file|file|...|file
for /r "%~dp1" %%x in (*.ts) do (
    if not defined list (
        set list=concat:%%~dpnxx
    ) else (
        set list=!list!^|%%~dpnxx
    )
)

:: create dir if not exist
if not exist "%out_dir%" (
    mkdir "%out_dir%"
)

:: create image sequence
ffmpeg.exe -i "%list%" -vf fps=fps=1/%thumb_every_seconds%,scale=iw/ih*%height%:%height% "%out_dir%%%06d.png"

and use it like this:

ffmpeg-create-thumbnails.bat ts-chunks-dir\ thumbnails-tmp-dir\

It will create sequence of images, captured from ts-chunks-dir directory of ts-chunk files and placed in thumbnails-tmp-dir temporary directory.

Step 2

Now with Montage utility from ImageMagick we create a one image of our previously created images. Create a batch file montage-thumbnails.bat with:

montage.exe -background black -mode concatenate -quality 70 "%~1" "%~2"

and use it like this:

montage-thumbnails.bat thumbnails-tmp-dir\ thumbnails.jpg

It will create thumbnails.jpg from sequence of images in thumbnails-tmp-dir directory.

Step 3

Add created before thumbnails image to player via flashvars:

var thumbnails = JSON.stringify({
    url: "thumbnails.jpg"
    , width: 128 // width of one image
    , height: 72 // height of one image
    , total: 71  // total images in this thumbnails
});
var flashvars = {
    ...
    , src_thumbnails: encodeURIComponent(thumbnails)
};

via media resource metadata in already loaded player:

var thumbnails = JSON.stringify({
    url: "thumbnails.jpg"
    , width: 128
    , height: 72
    , total: 71
});
player.setResourceMetadata("thumbnails", thumbnails);
player.load();

To remove thumbnails in already loaded player:

player.setResourceMetadata("thumbnails");
player.load();

Dynamic Buffering

... technique can guarantee a fast movie start time (using a low starting threshold) while ensuring an arbitrarily high resilience to bandwidth fluctuations. Resilience is limited only by memory usage. A buffer expanded to 100 seconds needs 3.7 MB for storing a 300 kbps stream or 6.2 MB for a 500 kbps stream. Because a seek in the middle of a stream empties the buffer, a too-large buffer can also cause a higher-than-average bandwidth consumption on the server side.

Fabio Sonnati in Implementing a dual-threshold buffering strategy in Flash Media Server

var flashvars = {
    ...
    , javascriptCallbackFunction: "onJSBridge"
}

...

var player;
var isStartedPlaying = false;
var initialBufferTime = 0.1;
var bufferTime = 4;
var expandedBufferTime = 10;

function onJSBridge(playerId, event, obj) {
    switch(event) {
        case "onJavaScriptBridgeCreated":
            player = document.getElementById(playerId);
            // add listener to handle buffering change event
            player.addEventListener("bufferingChange", "onBufferChange");
            break;

        // set buffer time to 0.1 for fast start
        case "ready":
        case "loading":
            isStartedPlaying = false;
            player.setBufferTime(initialBufferTime);
            break;

        // waiting playback starts
        case "playing":
            isStartedPlaying = true;
            break;

        // set buffer time to 0.1 for fast start at seeking
        case "seeking":
            player.setBufferTime(initialBufferTime);
            break;
    }
}

function onBufferChange(buffering) {
    // do not change buffer time before playback starts
    if (!isStartedPlaying) return;

    // if buffering is true, then player in buffering mode
    // and we set buffer time to 4 seconds
    // if buffering is false, then buffer is full and we can
    // expand buffer time to load more content to prevent
    // bandwidth fluctuations
    if (buffering) {
        player.setBufferTime(bufferTime);
    } else {
        player.setBufferTime(expandedBufferTime);
    }
}

Nginx Pseudo Streaming Plugin

Nginx Pseudo Streaming Plugin - is a plugin for OSMF framework that adds capability to playback pseudo streaming mp4 files.

Nginx mp4 module:

The pseudo-streaming works in alliance with a compatible Flash players. A player sends an HTTP request to the server with a start time specified in the query string argument (named simply start and specified in seconds), and the server responds with the stream such that its start position corresponds to the requested time, for example:

http://example.com/elephants_dream.mp4?start=238.88

Nginx Documentation

Using with Grind Player:

Download Nginx Pseudo Streaming Plugin or visit Nginx Pseudo Streaming Plugin on GitHub

var flashvars = {
    // mp4 url must be absolute
    src: "http://example.com/video.mp4"
    , src_nginxPseudoStreamingQuery: "start={0}"
    , plugin_pseudo: "NginxPseudoStreamingPlugin.swf"
    , autoRewind: false
}

Using with custom OSMF–based player:

  1. Load plugin:

    var factory:MediaFactory = new DefaultMediaFactory();
    var resource:URLResource = new URLResource("NginxPseudoStreamingPlugin.swf");
    factory.loadPlugin(resource);
    
  2. Add metadata

    var resource:StreamingURLResource = new StreamingURLResource("http://example.com/video.mp4");
    resource.addMetadataValue("nginxPseudoStreamingQuery", "start={0}");
    
  3. Create media

    var media:MediaElement = factory.createMediaElement(resource);
    var mediaPlayer:MediaPlayer = new MediaPlayer();
    mediaPlayer.media = media;
    

Known issues

  • Mp4 URL must be absolute
  • Will not properly show loaded bar in SMP or custom osmf-based player (need some customization)