About DVR support

Flash Media Server 3.5 or higehr

[adoberuntime]Flash Media Server 3.5 or higehr[/adoberuntime]

A DVR (digital video recorder) lets viewers pause live video and resumes playback from the paused location. Viewers can also rewind a live event, play the recorded section, and seek to the live section again. Just write a few lines of code and a video player can act like a DVR. Examples of DVR applications are instant replay and “catch-up” services.

Note:

The FLVPlayback component version 2.5 supports DVR. Download the component from the Adobe Media Server Tools

Using DVR with dynamic streaming

Flash Media Server 4.0 or higher, Flash Player
10.1

[adoberuntime]Flash Media Server 4.0 or higher, Flash Player 10.1[/adoberuntime]

To use DVR with a dynamic streaming application, enable absolute time code on the server. Absolute time code keeps DVR-enabled, multi-bitrate streams synchronized at the server.

The encoder must use absolute time code. Flash Media Live Encoder supports absolute time code, Flash Player does not. Third-party encoders also use absolute time code. Contact your encoder vendor to learn how to enable absoulte time code for your encoder.

Note:

The streams must be in sync, the server does not sync the streams. Absolute time code ensures that the server captures all the information required to keep encoder-synchronized streams in sync.

Enable absolute time code

Enable absolute time code in the Application.xml configuration file at the application level. When enabled, the server assumes that incoming live streams have timestamps that are based on an absolute clock, such as a SMPTE time signal contained within the encoder's input source. The default value is false.

  1. Create a new text or XML file and save it to the application’s folder as “Application.xml”.

    Note:

    For an example of an application-level Application.xml file, open rootinstall/applications/vod/Application.xml in a text editor.

  2. Add the following to the XML file:

    <Application> 
        <StreamManager> 
            <Live> 
                <AssumeAbsoluteTime>true</AssumeAbsoluteTime> 
            </Live> 
        </StreamManager> 
    </Application
  3. Save the file.

For more information about working with configuration files, see Configuring a single application.

Publish and record streams for DVR with dynamic streaming

To publish and record DVR streams with dynamic streaming, pass "appendWithGap" to the NetStream.publish() or Stream.record() methods. When you publish or record a stream in "appendWithGap" mode, the recorded stream preserves gaps created when a stream stops and restarts. In "append" mode, the server eliminates the gaps which can cause streams to lose sync. Gaps can occur when an encoder goes offline and comes back online. Gaps are visible to the client when a recorded file is played.

Publishing, playing, and seeking DVR video

To publish a stream for a DVR video player from a client, use "record" or "append" flags, as in the following:

NetStream.publish("myvideo", "record") 
NetStream.publish("myvideo", "append")

To publish a stream for a DVR video player from the server, call Stream.record(). The Stream.record() method has two new parameters, maxDuration and maxSize, that let you specify the maximum length and file size of a stream. The following code publishes a stream with a maximum recording length of 10 mins (600 seconds) and an unlimited file size:

Stream.record("record", 600, -1)

When a client plays a stream published for a DVR video player, they don’t play a live stream, they play a recorded stream. When a client views the stream “live”, they’re really viewing the recorded stream just after it was recorded.

To subscribe to a stream published for a DVR video player, use the following code:

NetStream.play("myvideo", 0, -1)

The previous code allows viewers joining an event late to view from the beginning.

To return to the beginning of a stream at any time, call the following:

NetStream.seek(0)

To start recording in the middle of an event, call the Server-Side ActionScript Stream.record() method. Calling this method lets you start and stop recording at any time.

To create a button that seeks to the latest available part of the recording (which is considered “live”), seek to the duration of the stream (the length of the data recorded on the server) minus the value of NetStream.bufferTime. Subtract bufferTime to make playback as close to instanteous as possible. Flash Player doesn’t resume playback until the buffer is full.

To calculate a stream's duration, set a time stamp in the onMetaData callback function.

public function onMetaData(info:Object):void{ 
    trace("metadata:duration=" + info.duration); 
    duration = info.duration; 
    trace("stamp: " + stamp); 
    stamp = getTimer(); 
}

To calculate the value to pass to the seek() method, calculate the current duration, and subtract the bufferTime minus an additional 2 seconds for safety.

private function getSeekToLiveValue():uint{ 
    currentDuration = Number((getTimer()-stamp)/1000) + duration; 
    trace("currentDuration: " + currentDuration); 
    seekVal = (currentDuration - nsPlayer.bufferLength) - 2; 
    return seekVal; 
}

To seek to “live”, call the getSeekToLiveValue() function on the playback NetStream object:

private function onClick(event:MouseEvent):void { 
    switch(event.currentTarget){ 
        case rewindBtn: 
            nsPlayer.seek(nsPlayer.time - 5); 
            break; 
        case seekBtn: 
            trace("seekToEndValue " + getSeekToEndValue()); 
            nsPlayer.seek(getSeekToLiveValue()); 
            break; 
    } 
}

Note:

The previous code is in the DVR sample in the rootinstall/documentation/samples/dvr folder on the server.

Using Flash Media Live Encoder to capture video for DVR playback

You can use Flash Media Live Encoder 3 to capture video for DVR playback. Earlier versions of Flash Media Live Encoder do not support recording to the server. For more information, see http://www.adobe.com/go/fme.

Example: Custom capture, publish, and DVR playback

This example is a client application that does the following:

  • Captures and encodes video.

  • Displays the video as it’s captured.

  • Streams video from the client to Adobe Media Server.

  • Streams video from Adobe Media Server back to the client.

  • Displays the video streamed from the server in a player that lets you rewind and pause live video.

Note:

To test this code, create a RootInstall/applications/dvr folder on the server. Open the RootInstall/documentation/samples/dvr/DVR.swf file to connect to the application.

  1. On Adobe Media Server, create a RootInstall/applications/dvr folder.

  2. In Flash, create an ActionScript file and save it as DVR.as.

  3. Copy and paste the following code into the Script window:

    package { 
        import flash.display.MovieClip; 
        import flash.utils.getTimer; 
        import flash.net.NetConnection; 
        import flash.events.*; 
        import flash.net.NetStream; 
        import flash.media.Video; 
        import flash.media.Camera; 
        import flash.media.Microphone; 
        import fl.controls.Button; 
        public class DVR extends MovieClip 
        { 
            private var nc:NetConnection; 
            private var ns:NetStream; 
            private var nsPlayer:NetStream; 
            private var vid:Video; 
            private var vidPlayer:Video; 
            private var cam:Camera; 
            private var mic:Microphone; 
            private var pauseBtn:Button; 
            private var rewindBtn:Button; 
            private var playBtn:Button; 
            private var seekBtn:Button; 
            private var dvrFlag:Boolean; 
            private var stamp:uint; 
            private var duration:uint; 
            private var currentDuration:uint; 
            private var seekVal;uint; 
            public function DVR() 
            { 
                nc = new NetConnection(); 
                nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); 
                nc.connect("rtmp://localhost/dvr"); 
                setupButtons(); 
                dvrFlag = true; 
            } 
            private function onNetStatus(event:NetStatusEvent):void{ 
                trace(event.info.code); 
                switch(event.info.code){ 
                    case "NetConnection.Connect.Success": 
                        publishCamera(); 
                        displayPublishingVideo(); 
                        displayPlaybackVideo(); 
                        break; 
                    case "NetStream.Play.Start": 
                        trace("dvrFlag " + dvrFlag); 
                        if(dvrFlag){ 
                            nsPlayer.seek(getSeekToLiveValue()); 
                            dvrFlag = false; 
                        } 
                        break; 
                } 
            } 
            private function onAsyncError(event:AsyncErrorEvent):void{ 
                    trace(event.text); 
            } 
            private function onClick(event:MouseEvent):void { 
                switch(event.currentTarget){ 
                    case rewindBtn: 
                        nsPlayer.seek(nsPlayer.time - 5); 
                        break; 
                    case pauseBtn: 
                        nsPlayer.pause(); 
                        break; 
                    case playBtn: 
                        nsPlayer.resume(); 
                        break; 
                    case seekBtn: 
                        nsPlayer.seek(getSeekToLiveValue()); 
                        break; 
                } 
            } 
            private function publishCamera() { 
                cam = Camera.getCamera(); 
                mic = Microphone.getMicrophone(); 
                ns = new NetStream(nc); 
                ns.client = this; 
                ns.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); 
                ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); 
                ns.attachCamera(cam); 
                ns.attachAudio(mic); 
                ns.publish("video", "record"); 
            } 
            private function displayPublishingVideo():void { 
                vid = new Video(cam.width, cam.height); 
                vid.x = 10; 
                vid.y = 10; 
                vid.attachCamera(cam); 
                addChild(vid); 
            } 
            private function displayPlaybackVideo():void{ 
                nsPlayer = new NetStream(nc); 
                nsPlayer.client = this; 
                nsPlayer.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); 
                nsPlayer.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); 
                nsPlayer.play("video", 0, -1); 
                vidPlayer = new Video(cam.width, cam.height); 
                vidPlayer.x = cam.width + 20; 
                vidPlayer.y = 10; 
                vidPlayer.attachNetStream(nsPlayer); 
                addChild(vidPlayer); 
            } 
             
            private function getSeekToLiveValue():uint{ 
                currentDuration = Number((getTimer()-stamp)/1000) + duration; 
                trace("currentDuration: " + currentDuration); 
                seekVal = (currentDuration - nsPlayer.bufferTime) - 2; 
                trace("seekVal: " + seekVal); 
                return seekVal; 
            } 
            private function setupButtons():void { 
                rewindBtn = new Button(); 
                pauseBtn = new Button(); 
                playBtn = new Button(); 
                seekBtn = new Button(); 
                rewindBtn.width = 52; 
                pauseBtn.width = 52; 
                playBtn.width = 52; 
                seekBtn.width = 100; 
                rewindBtn.move(180,150); 
                pauseBtn.move(235,150); 
                playBtn.move(290,150); 
                seekBtn.move(345, 150); 
                rewindBtn.label = "Rew 5s"; 
                pauseBtn.label = "Pause"; 
                playBtn.label = "Play"; 
                seekBtn.label = "Seek to Live"; 
                rewindBtn.addEventListener(MouseEvent.CLICK, onClick); 
                pauseBtn.addEventListener(MouseEvent.CLICK, onClick); 
                playBtn.addEventListener(MouseEvent.CLICK, onClick); 
                seekBtn.addEventListener(MouseEvent.CLICK, onClick); 
                addChild(rewindBtn); 
                addChild(pauseBtn); 
                addChild(playBtn); 
                addChild(seekBtn); 
            } 
             
            public function onMetaData(info:Object):void { 
                   trace("metadata:duration = " + info.duration); 
                stamp = getTimer(); 
                trace("stamp: " + stamp); 
                duration = info.duration; 
            } 
        } 
    }
  4. Save the DVR.as file.

  5. Choose File > New > Flash File (ActionScript 3.0) and click OK.

  6. Save the file as DVR.fla in the same folder as the DVR.as file.

  7. Open the Components Panel, drag a Button to the Stage, and delete it.

    This action adds the button to the Library. The button is added to the application at runtime.

  8. Choose File > Publish Settings. Click the Flash tab. Click Script Settings and enter DVR as the Document class. Click the checkmark to validate the path.

  9. Save the file and choose Control > Test Movie to run the application.

Limiting the size and duration of recordings

You can limit the maximum size and duration of recordings using parameters in the Application.xml configuration file, Server-Side ActionScript, and the Authorization plug-in. Set these values to prevent using excessive disk space. The following are the Application.xml file parameters:

XML element

Description

Application/StreamManager/Recording/MaxDuration

The maximum duration of a recording, in seconds. The default value is -1, which enforces no maximum duration.

Application/StreamManager/Recording/MaxDurationCap

The maximum duration of a recording cap, in seconds. The default value is -1, which enforces no cap on maximum duration.

The server-side Stream.record() method cannot override this value. The Authorization plug-in can override this value.

Application/StreamManager/Recording/MaxSize

The maximum size of a recording, in kilobytes. The default value is -1, which enforces no maximum size.

Application/StreamManager/Recording/MaxSizeCap

The maximum size of a recording cap, in kilobytes. The default value is -1, which enforces no cap on maximum size.

The server-side Stream.record() method cannot override this value. The Authorization plug-in can override this value.

In addition to setting maximum values, in the Application.xml file you can set maximum cap values. Server-side scripts cannot override these caps. CDNs can use these caps to set a limit that clients cannot override.

Note:

The Authorization plug-in can override any values set in the Application.xml file.

To set values in Server-Side ActionScript, call Stream.record() and pass values for the maxDuration and maxSize parameters. The following code limits the recording to 5 minutes and sets an unlimited maximum file size (up to the value of MaxSizeCap):

s.record("record", 300, -1);

The server truncates recordings greater than MaxCapSize and MaxCapDuration.

Scaling DVR applications

To build large-scale applications, use the server-side NetConnection class to chain multiple servers together. In this scenario, a client can request a stream that does not reside on the server to which it is connected. Use the server-side ProxyStream class to create a look-up mechanism for finding streams in the server chain.

You can set values in the Vhost.xml configuration file to configure the disk cache that holds the streams:

XML element

Attribute

Description

VirtualHost/Proxy/CacheDir

enabled

Determines whether the disk cache is enabled.

useAppDir

Specifies whether to separate the cache subdirectories by application.

VirtualHost/Proxy/CacheDir/Path

The root directory of the disk cache.

VirtualHost/Proxy/CacheDir/MaxSize

The maximum size of the disk in gigabytes. The default value is 32. The value 0 disables the disk cache. The value -1 specifies no maximum value.

VirtualHost/Proxy/RequestTimeout

The maximum amount of time to wait for a response to a request (for metadata, content, and so on) from an upstream server., in seconds. The default value is 2 seconds.

If a server has multiple virtual hosts, point each virtual host to its own cache directory.

If the server runs out of disk space on an intermediate or edge server while writing to the CacheDir, it logs the following warning message in the core.xx.log for each segment that fails to write to disk:I/O Failed on cached stream file C:\Program Files\Adobe\Adobe Media Server 3.5\cache\streams\00\proxyapp\<IP>\C\Program Files\Adobe\Adobe Media Server 3.5_361\applications\primaryapp\streams\_definst_\sample1_1500kbps.f4v\0000000000000000 during write: 28 No space left on device.

Logging

Streams played in a DVR video player are played as recorded streams. These streams log the same events in the log files as every recorded stream.

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy