This is a project I have been working on the last weeks, which I wanted to share since it is applicable to a lot of existing red-button advertisement workflows. 5 years ago I wrote an Adobe Developer Center article that outlined how to inject live metadata into a live stream. This concept since then has been used to inject not just metadata, but also trigger live stream ad breaks. As a matter of fact, most Flash-based live ad workflows use this technique.
Options to inject ad markers
Before I go deeper into how Adobe Primetime helps to expand this workflow to mobile, it’s important to understand what the overall options are to inject ad breaks – Adobe Primetime supports all of them, meaning the workflow described in this article is not the only option, but it is the most compatible one if you already have a workflow like this in place.
1. Ad cuepoint injection with encoder
Many encoders have the option to inject ad cue points for RTMP over an API. Overall that reduces the complexity of the setup, with the only downside that building out a custom ad injection interface might be slightly more complex, since it’s hard to pull the live stream accurately into an operator interface to sync the ad breaks visually to the stream. The workaround is to work with absolute time code.
For HLS and HDS ad break triggering, injecting it on the ad-enabled encoder is the best approach, because it’s setting keyframes at the ad break points. This allows frame accurate ad insertion with stitching technologies, which is specifically important for linear workflows where broadcast ads are replaced with dynamic ads. This is also important for RTMP workflows – instead of triggering the ad breaks with Adobe Media Server as described in this article, and using an encoder with RTMP ad injection API instead, Adobe Primetime would benefit from correct keyframes at the ad cue points, which increases the ad placement accuracy.
2. Ad cuepoint injection with Adobe Media Server
I am reviving this old diagram from my 2008 Developer Center article to explain the technique. The product names changed, but the concept is the same.
Adobe Media Server is located between the encoder and the CDN / Flash Player, and has two purposes:
- Receive an RTMP stream from the encoder, and republish it to the CDN / Flash Player with the multi-point publish feature.
- Send a low latency live preview to the ad cue point application, and receive commands from the same to inject ad cue points.
The advantage of this workflow is full independence from the encoder, since ad insertion is handled by the Adobe Media Server layer. The disadvantage is once the stream is getting repackaged to HLS, the ad insertion won’t be frame accurate, since Adobe Media Server can’t inject keyframes – this is only possible at encoding time on the encoder.
How to use Adobe Primetime to extend this workflow to mobile
The prerequisite is Adobe Media Server is used as the cue point injection mechanism, since it doesn’t introduce any encoder dependence, and which I assume, at least for this article, is already in place.
This is the starting architecture.
With the Adobe Primetime Packager, Adobe Primetime Origin and the Adobe Primetime Player, the architecture evolves to the following.
Adobe Media Server
The Adobe Media Server runs an extended script based on the original Dev Center prototype.
Besides republishing the stream and injection of the cue points, it also includes additional logic to properly handle streams events to correctly send absolute time code information to the Adobe Primetime Packager.
function sendDPIAdCue(stream, id, duration) { trace("sending ad cue, duration: " + duration); var params = {}; params["type"] = "spliceOut"; params["id"] = id; params["time"] = 0; params["duration"] = Number(duration); stream.send("onAdCue",params); } function sendAdSignal (data) { trace("##Sending Ad cues to all streams."); for(var sname in application.smap) { var ns = application.smap[sname]; sendDPIAdCue(ns, "Cue" + application.adCueId,data); } ++application.adCueId; } application.onAppStart = function() { application.lastCue = 0; application.smap = {}; application.adCueId = 1; } application.onPublish = function(client, myStream) { trace(myStream.name + " is publishing into application " + application.name); if(client.ptConnArray == undefined) client.ptConnArray = []; var nc = new NetConnection(); nc.connect( "rtmp://myprimetimeserver.com/livepkgr" ); trace("Connected to livepkgr"); var ns = new NetStream(nc); // called when the server NetStream object has a status ns.onStatus = function(info) { trace("Stream Status: " + info.code) if (info.code == "NetStream.Publish.Start") { trace("The stream is now publishing"); } } nc.onStatus = function(info) { if (info.code == "NetConnection.Connect.Success") { ns.setBufferTime(2); ns.attach(myStream); ns.publish( myStream.name); trace("Publishing " + myStream.name); application.smap[myStream.name] = ns; myStream.ptNetConn = nc; client.ptConnArray.push(nc); } } } application.onUnpublish = function(client, myStream) { var nc = myStream.ptNetConn; if(nc != undefined) { trace("Client unpublished: " + myStream.name); nc.close(); } } application.onDisconnect = function (client){ trace("Client disconnected, will close all upstream connections:" + client.ptConnArray.length); for(var i = 0; i < client.ptConnArray.length; ++i) { var nc = client.ptConnArray[i]; trace("###Closing outbound connection:" + i); nc.close(); } } Client.prototype.Splice = sendAdSignal; |
The ad insertion console for this test is very simple, it only displays the video and provides an input form to inject the ad marker length. The timing is defined by when the operator hits the send button.
This interface is intended for the ad operator user role, meaning there is a lot that can be added to provide an easy to use experience. This application doesn’t have to be operated on the Adobe Media Server, but can be a normal website accessible to the ad operations team.
package { import flash.display.MovieClip; import flash.net.NetConnection; import flash.net.NetStream; import flash.media.Video; import flash.events.*; import flash.media.Video; import flash.net.NetConnection; import flash.net.NetStream; public class main extends MovieClip { private var video_ns:NetStream; private var video_nc:NetConnection; public function main() { video_nc = new NetConnection(); video_nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); video_nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); video_nc.connect("rtmp://myamsserver.com/adrelay"); this["sync_btn"].addEventListener(MouseEvent.CLICK, injectCue); } private function injectCue(e:MouseEvent):void { video_nc.call("Splice",null,inputfield_txt.text); } private function netStatusHandler(event:NetStatusEvent):void { trace(event.info.code); switch (event.info.code) { case "NetConnection.Connect.Success" : connectStream(); break; case "NetStream.Play.StreamNotFound" : trace("Unable to locate video"); break; } } private function connectStream():void { video_ns = new NetStream(video_nc); video_ns.bufferTime = 1; video_ns.play("jens"); video_ns.client = new CustomClient(); this["videoarea"].attachNetStream(video_ns); } private function securityErrorHandler(event:SecurityErrorEvent):void { trace("securityErrorHandler: " + event); } } } |
To get access to the Adobe Primetime related components, please contact the team directly,
Player
The player for both desktop and mobile in this setup is the Adobe Primetime Player. Besides solving the Android video problem, it also enables a true seamless ad experience, which is very different from what is possible with dual player approaches with OSMF or on mobile today. On mobile, seamless ad insertion is a must have component.
In addition, if content protection or DRM is required, the Adobe Primetime Packager can easily enable it for both HDS and HLS, and the Adobe Primetime Player has the necessary DRM components embedded.
Conclusion
This is a great example how existing workflows can be easily extended to support mobile with the right technologies. I am personally excited about this setup, since it builds on top of solution I worked on a couple of years ago.
It is important to note that if you are comparing an encoder based ad triggering workflow with the Adobe Media Server approach, I would recommend the encoder approach since it’s the only part of the workflow where keyframes for ad breaks can be injected. If that’s not an option, and keyframe accurate ad insertion is not a requirement, the Adobe Media Server approach provides a lot of flexibility and independence from the encoder.