Dynamic streaming (Adaptive bitrate) using Flash media Server

Video and the web are long time friends already, you see lots of websites with some sot of player for video content, you also see media companies and television channels serving video over the wire to the end users.

Video can be downloaded (progressive download) to the client like YouTube or can be streamed to him.

I won’t discuss the pro’s & con’s of streaming and progressive, we will only be discussing streaming in this post.

So, What is the post worthy problem we have when streaming video to the client?

Continue reading and find out.
When streaming video, you usually encode your video in a certain bitrate, this bitrate will work well in certain computers and won’t work well in others.

Why is that?

That’s because each of your users has a different connection thus being capable to receive a limited amount of bytes streamed to him.

So, you always have to find the “sweet spot” where you deliver a video that looks good and also light enough to fit a wider range of network connection speeds.

That is of course a problem becuase sometimes you want to stream hiQuality video and audio (HD) and you can’t because some of your users won’t be able to watch it smoothly.

Well, this is a problem we will solve here in this post.

Flash media server introduced a new way of streaming video to the client, it is called dynamic streaming and it is very dynamic (name implies just that).

That way, you can create several encodes to the same source movie and supply a movie best fit to the user cosuming it.

Another obvious advantage of this approach is the way it skips on top of the movie, when you skip to a further point in the movie, you are actually being supplied a different movie from the lowest quality to the highest.

So, how is it done, first, let’s create a flash file (I’m using flash CS4).

Now, we will create (by dragging and dropping from the components panel) an FLVPlayback control.

We will give it the same dimensions as the movie itself so it will fill the entire surface.

Now, we will give the player an instance name of “flvPlayer”

so, we have a movie, we have an FLVPlayback control, we called it “flvPlayer”.

We will create another layer, called “action script” and we will create this code inside.

import fl.video.*;
VideoPlayer.iNCManagerClass = NCManagerDynamicStream;
flvPlayer.source = "dynamicStream.smil";

OK, so as we can see we created a pointer to a file called “dynamicStream.smil”, this is a special file (XML format).

This file details the following

  • FMS Url
  • files you have encoded and a network speed in correlation

The file looks like this:

<smil>
    <head>
        <meta base="rtmp://your_fms_location/vod/" />
    </head>
    <body>
	<switch>
		<video src="mp4:erets7_03_vod1_300.mp4" system-bitrate="300000"/>
		<video src="mp4:erets7_03_vod1_500.mp4" system-bitrate="500000"/>
		<video src="mp4:erets7_03_vod1_800.mp4" system-bitrate="800000"/>
        </switch>
    </body>
</smil>

Now, when we put this file on our web-server, the user will get the file it can receive and play, not slower, not faster, just the perfect file for him.

NO buffering, NO waiting, NO frustration, smooth, cool and fast user experience.

Good luck.

You can download the complete solution from here:
Source - Dynamic streaming (flash media server) (112)

Avi Tzurel

My name is Avi Tzurel. I'm a professional web developer from Israel. I spend most of my day developing both web products and RIA applications as well as imparting my experience onto others. I speak, teach and write about my passions, and develop applications according to what I preach. I specialize in Flex, Adobe Air, HTML, XHTML, Javascript, jQuery and other Javascript libraries, on the server side I do .net along side with Ruby on Rails. You can connect with me on Twitter or email me through the contact page on this blog.

  • Share/Bookmark
Posted Thursday, April 15th, 2010 under Flash media server, Flex, General, actionscript.

10 comments

  1. Dileep Kumar says:

    Hi

    i am playing advertisement video with another video(movies).i am streaming video(movies)

    using FMS and advertisement video using HTTP protocol . i pause running video and play

    advertisement video at specified position .but i am facing one problem . problem is here.
    whenever advertisement video play then running video is paused . playhead for running video

    is moved more than specified position but advertisement video play fine.

    For Example:- when i am playing advertisement video at 30 seconds .then running video pause at

    30 seconds and playhead should be at 30 seconds until advertisement video is completed but

    playhead is moved more 30 seconds.

    so please tell me about “why is this happening ” and ” what am i doing wrong”

    Thanks in advance

  2. @Dileep Kumar:
    I don’t quite understand what your problem is.
    I will need to see some code or some sort of example to see what the problem is.

    Thanks,

  3. Dileep Kumar says:

    i am sending code .so please check it.

    public class ClassVideoPlayer{
    //VARS
    private var targetRoot : Object
    private var root : MovieClip
    public var videoSprite : Sprite
    public var video_width : Number
    public var video_height : Number
    private var recommendWidth : Number
    private var recommendHeight : Number
    private var video_duration : Number
    public var vscale : String
    private var streamPath : String
    public var serverType : String // -http -rtmp
    private var liveStreamPath : String
    private var liveStreamServer : String
    private var myVideo : Video
    private var nc : NetConnection
    private var ns : NetStream
    private var nsCallBack : Object
    private var onvideoload : Function
    private var videopreload : Function
    public var onvideoend : Function
    private var videoPreloadTimer : Timer
    private var bufferTimer : Timer;
    public var vstatus : String
    public var vloop : Boolean
    private var onbufferFull : Function
    private var onbufferEmpty : Function
    public var LimelightServer : Boolean
    public var ADflag : Number=0;
    public var rflag : Number=0
    var adflag : Number=0;
    // CONSTANTS
    private const CONST_video_width = 320
    private const CONST_video_height = 240

    public const VIDEO_PLAYING = ‘playing’
    public const VIDEO_STOP = ‘stop’
    public const VIDEO_PAUSE = ‘pause’

    function ClassVideoPlayer(settings) {
    root = ClassUtils.root
    setDefaultValues()
    targetRoot = settings.target
    vscale = settings.vscale
    recommendWidth = settings.recommendWidth
    recommendHeight = settings.recommendHeight
    onvideoload = settings.onvideoload
    videopreload = settings.videopreload
    onvideoend = settings.onvideoend
    vloop = settings.vloop
    nsCallBack = new Object()
    if(typeof(settings.onbufferFull)== ‘function’){
    onbufferFull = settings.onbufferFull
    }
    if(typeof(settings.onbufferEmpty)== ‘function’){
    onbufferEmpty = settings.onbufferEmpty
    }
    videoPreloadTimer = new Timer(50)
    //bufferTimer = new Timer(50);
    videoPreloadTimer.addEventListener(TimerEvent.TIMER, getvideoload)
    //bufferTimer.addEventListener(TimerEvent.TIMER,checkbufferStatus);
    var ncclient : Object = new Object()
    ncclient.onBWDone = onBWDone
    ncclient.onFCSubscribe = onFCSubscribe

    videoSprite = new Sprite()
    videoSprite.name = settings.targetName
    targetRoot.addChild(videoSprite)
    myVideo = new Video()
    myVideo.smoothing = true
    videoSprite.addChild(myVideo)
    nc = new NetConnection()
    nc.client = ncclient
    nc.connect(null)
    nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
    nc.addEventListener(NetStatusEvent.NET_STATUS, netConnectionHandler);
    initNetStream()
    }
    // AsyncErrorEvent
    function asyncErrorHandler(event:AsyncErrorEvent):void {
    //trace(event.text);
    }
    //Handle the Call
    function onBWDone(…args):void{
    //trace ( “onBWDone! : ” + args );
    }
    // Limelight Server
    public function onFCSubscribe(info:Object):void {
    initNetStream()
    ns.play(info.description)
    }
    // INIT NET STREAM
    function initNetStream() {
    nsCallBack.onMetaData = onMetaDataHandler
    ns = null
    ns = new NetStream(nc)
    ns.client = nsCallBack
    //ns.bufferTime = 10
    ns.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler)
    myVideo.attachNetStream(ns)
    }
    // NET STATUS EVENT
    function netConnectionHandler(event:NetStatusEvent):void {
    //trace(event.info.code)
    if (event.info.code == ‘NetConnection.Connect.Rejected’) {
    root.classError.show(‘Error!\n’+'Server Rejected Connection’)
    }
    if (event.info.code == “NetConnection.Connect.Success” && serverType == ‘rtmp’) {
    if (LimelightServer) {
    nc.call(‘FCSubscribe’, null, liveStreamPath);
    }else {
    trace(‘initNetStream’)
    initNetStream()
    this.liveplay()
    }
    }
    }
    // SET DEFAULT VALUES
    private function setDefaultValues():void{
    video_width = CONST_video_width
    video_height = CONST_video_height
    vscale = ‘original’
    vloop = false
    }
    // META DATA INFO
    private function onMetaDataHandler(metaInfoObj:Object):void{
    if(Number(metaInfoObj.width)>0) video_width = metaInfoObj.width
    if(Number(metaInfoObj.height)>0) video_height = metaInfoObj.height
    if(Number(metaInfoObj.duration)>0) video_duration = metaInfoObj.duration
    setVideoSize()
    for(var key:String in metaInfoObj){
    trace(key+’= ‘+metaInfoObj[key])
    }
    }
    // NetStatusEvent.NET_STATUS
    private function netStatusHandler(e:NetStatusEvent):void{
    if(e.info.code)
    if(e.info.code == ‘NetStream.Play.Start’){
    trace(“Start”)
    setVideoSize()
    }

    if(e.info.code == ‘NetStream.Buffer.Full’){
    if(this.onbufferFull!=null){
    this.onbufferFull()
    }
    }
    if( e.info.code == ‘NetStream.Seek.InvalidTime’){
    trace(“File is not well structured”)
    }
    if(e.info.code == ‘NetStream.Buffer.Empty’ || e.info.code == ‘NetStream.Seek.InvalidTime’ || e.info.code == ‘NetStream.Play.Start’){

    if(this.onbufferEmpty!=null){
    this.onbufferEmpty()
    }
    }
    if(e.info.code == ‘NetStream.Play.Stop’){
    vstatus = VIDEO_STOP
    trace(adflag)
    if(adflag==1){
    root.classNavigation.ShowRelatedVideo();
    }else{
    if(this.vloop){
    this.play()
    }else{
    if(this.onvideoend!=null){
    this.onvideoend()

    }
    }
    }
    }
    }
    // SET VIDEO SIZE
    private function setVideoSize():void{
    manualVideoSize(recommendWidth,recommendHeight,vscale)
    }
    // SET MANUAL VIDEO SIZE
    public function manualVideoSize(vwidth,vheight,vscale,noevent:Boolean=false):void{
    var coef:Number
    if(vscale == ‘fitw’){
    myVideo.width = vwidth
    coef = video_height/video_width
    myVideo.height = vwidth * coef
    }
    if(vscale == ‘fith’){
    myVideo.height = root.stage.stageHeight
    //coef = video_width/video_height
    myVideo.width = root.stage.stageWidth
    }
    if(vscale==’scale’){
    myVideo.width = vwidth
    myVideo.height = vheight
    }
    if(vscale==’original’){
    myVideo.width = video_width
    myVideo.height = video_height
    }
    if(onvideoload!=null && !noevent){
    onvideoload()
    }
    }
    // LIVE PLAY RTMP
    private function liveplay(){
    trace(liveStreamPath)
    ns.play(String(liveStreamPath))
    }
    // PLAYER PLAY
    public function play(streamPath = undefined, duration = undefined):void {
    this.stop()
    if(duration!=undefined){
    video_duration = duration
    }
    if(streamPath == undefined){
    streamPath = this.streamPath
    }else{
    this.streamPath = streamPath
    }
    if(root.classMediaAd==undefined){

    adflag=1;
    }else{
    if(!(root.classMediaAd.adModeON)){

    adflag=1
    }else{
    adflag=0
    }
    }
    if(streamPath.indexOf(‘rtmp://’)!=-1){
    serverType = ‘rtmp’
    liveStreamPath = streamPath
    liveStreamServer = streamPath
    liveStreamServer = liveStreamServer.substr(0,liveStreamServer.lastIndexOf(‘/’)+1)
    liveStreamPath = liveStreamPath.substr(liveStreamServer.lastIndexOf(‘/’)+1, (liveStreamPath.length – liveStreamServer.lastIndexOf(‘/’)))
    nc.connect(liveStreamServer)

    ///ns.play(streamPath)
    }else{
    serverType = ‘http’
    nc.connect(null)
    initNetStream()
    trace(streamPath)
    ns.play(String(streamPath))
    }

    videoPreloadTimer.start()
    vstatus = VIDEO_PLAYING
    }
    // PLAYER STOP
    public function stop():void{
    ns.close()
    vstatus = VIDEO_STOP
    }
    // PLAYER PAUSE
    public function pause():void {
    if (vstatus == VIDEO_PLAYING) {
    ns.pause()
    vstatus = VIDEO_PAUSE
    }
    }
    function AddVideo(ad:Object):void{

    var VidSprite:Sprite=new Sprite();
    myVideo.width=301;
    myVideo.height=214;
    myVideo.x=-150.5;
    myVideo.y=-107;
    VidSprite.addChild(myVideo);
    ad.addChild(VidSprite);
    }
    function ReturnVideo():void{
    //SEFlag=0;
    videoSprite.addChild(myVideo);
    targetRoot.addChild(videoSprite);
    myVideo.x=0;
    myVideo.y=0;
    setVideoSize();

    }

    // PLAYER RESUME PLAY
    public function resume():void{
    ns.resume()
    vstatus = VIDEO_PLAYING
    }
    // PLAYER togglePause
    public function togglePause():void{
    ns.togglePause()
    vstatus = VIDEO_PAUSE
    }
    // GET CURRENT TIME
    public function get time():Number{
    //trace(ns.time)
    return ns.time
    }
    // GET VIDEO DURATION
    public function get duration():Number{
    return video_duration
    }
    // SEEK VIDEO
    public function seek(percent):void{
    var newseek = percent/100*video_duration
    ns.seek(newseek)
    }
    // PRELOAD VIDEO
    private function getvideoload(e:TimerEvent):void{
    if(this.getBytesTotal == this.getBytesLoaded){
    videoPreloadTimer.stop()
    if(videopreload!=null){
    videopreload(true)
    }
    }else{
    if(videopreload!=null){
    videopreload(false)
    }
    }
    }
    // GET VIDEO TOTAL BYTES
    public function get getBytesTotal():Number{
    return ns.bytesTotal
    }
    // GET VIDEO LOADED BYTES
    public function get getBytesLoaded():Number{
    return ns.bytesLoaded
    }
    // SET VOLUME
    public function set volume(value):void{
    ns.soundTransform = new SoundTransform(value)
    }
    // GET VOLUEM
    public function get volume():Number{
    return ns.soundTransform.volume
    }
    // SET VIDEO FILE
    public function set file(value):void{
    streamPath = value
    }
    // SET CUSTOM VIDEO SIZE
    public function setCustomVideoSize(vwidth,vheight):void{
    recommendWidth = vwidth
    recommendHeight = vheight
    }
    // SET BUFFER TIME
    public function set bufferTime(value):void{
    ns.bufferTime = value
    }
    // GET BUFFERING VALUE
    public function getBuffering():Number{
    var value = 0
    value = Math.round(ns.bufferLength / ns.bufferTime*100)
    return value
    }
    // SET VIDEO SMOOTHING
    public function set smoothing(value:Boolean):void{
    myVideo.smoothing = value
    }
    // GET PERCENT OF PLATING STREAM
    public function get percent():Number{
    return (time/duration)
    }
    // CLEAR VIDEO STAGE
    public function clear(){
    myVideo.clear()
    }
    }
    }

    i am creating two object of this class that i am sending code for u . first object for playing movie and another is for playing advertise video.if i play movies and advertise video by using web server(http protocol). then it works fine. but just i play movie by using rtmp protocol .then i face this type of problem

    i am explaining what i am doing

    Actually i want to play advertise video at particular periods when movie is running.so i have created two class . one for playing and handling video and another is for checking time periods to play advertise video.
    it is working fine by using http protocol. both movie and advertise video are playing properly.but it is not working with rtmp protocol .
    problem only is here. movie start properly. as playhead reaches to particular period. playhead immediately goes ahead. after moving, movies is paused and start playing advertise video.
    i have reviewed code more time . but i am unable to understand this problem.
    so please guide me what to do

    Thanks in advance

  4. Great, I never knew this, thanks.

  5. Hi

    i am playing advertisement video with another video(movies).i am streaming video(movies)

    using FMS and advertisement video using HTTP protocol . i pause running video and play

    advertisement video at specified position .but i am facing one problem . problem is here.
    whenever advertisement video play then running video is paused . playhead for running video

    is moved more than specified position but advertisement video play fine.

    For Example:- when i am playing advertisement video at 30 seconds .then running video pause at

    30 seconds and playhead should be at 30 seconds until advertisement video is completed but

    playhead is moved more 30 seconds.

    so please tell me about “why is this happening ” and ” what am i doing wrong”

    Thanks in advance

  6. It’s posts like this that keep me coming back and checking this site regularly, thanks for the info!

  7. Keep posting stuff like this i really like it

  8. Hello,

    do you know if FMS only falls back to lower quality when an insufficient connection is in place or it will get back to the top bitrate if connection improves?

  9. @Nikola:
    The answer to your question is yes, Flash and FMS will continue checking for your connection quality and move you back and forth between streams.

Leave a Reply