How to play YuMe video ads in Adobe Flex/AS3
Posted by Ian Serlin | Tweet This | Filed under Actionscript 3, Adobe Flex, problem solution
Problem
Compared to a lot of other Internet video advertising services, YuMe has a pretty low barrier to entry because they don’t limit who can join as a publisher based on your current visitor volume or anything like that: simply sign-up and get access to the SDK. On the other hand, the documentation you receive along with the SDK is so convoluted that it turns what should be a 5 minute job into a goose hunt. What’s a goose hunt? Exactly, but once you understand what’s going on it’s pretty sweet.
Solution
I’ve created a simple Flex wrapper around the YuMe video player to get you up and running quickly. You’ll need to signup with YuMe to get access to the SDK and their required library swfs.
Feel free to add and improve upon the code or post your own wrapper and link to it in the comments!
YuMeVideoAd.mxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | <?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Metadata> [Event(name="ad_completed", type="flash.events.Event")] [Event(name="ad_absent", type="flash.events.Event")] [Event(name="ad_error", type="flash.events.Event")] </mx:Metadata> <mx:Script> <![CDATA[ import mx.controls.SWFLoader; import mx.core.FlexMovieClip; // Possible ad types public static const BEFORE_CONTENT:String = "before_content"; public static const DURING_CONTENT:String = "during_content"; public static const AFTER_CONTENT:String = "after_content"; /** * URL to where you are hosting "yume_player_4x3.swf" */ public static var yume_player_swf_url:String = "yume_player_4x3.swf"; /** * URL to where you are hosting "yume_ad_library.swf" */ public static var yume_library_swf_url:String = "yume_ad_library.swf"; /** * URL to where you are hosting any specific CSS associated with the YuMe ads you are displaying. */ public var yume_css_url:String = ""; public var ad_width:Number = 480; public var ad_height:Number = 360; /** * The type of ad to play in this player the next time startAd() is called, * valid values are: "before_content", "during_content", "after_content" */ public function set ad_type( value:String ):void { // set ad type yume_params.ad_type = value; regeneratePlaylistURL(); } /** * Help YuMe target the video ad by passing tags related to the content this ad will be shown with. */ public function set tags( value:String ):void { _tags = value; regeneratePlaylistURL(); } /** * Help YuMe target the video ad by passing the title of the content this ad will be shown with. */ public function set title( value:String ):void { _title = value; regeneratePlaylistURL(); } /** * Help YuMe target the video ad by passing the duration of the content this ad will be shown with. */ public function set duration( value:Number ):void { _duration = value; regeneratePlaylistURL(); } /** * Help YuMe advertisers target your company's content by giving them a way to identify you. */ public function set channel( value:String ):void { _channel = value; regeneratePlaylistURL(); } /** * Any YuMe specific errors caught by the YuMe player. * You should check this value if you catch an "ad_error" event. */ public function get errors():String { return _yume ? _yume.error_string : null; } /** * The object the YuMe interface will be loaded into and accessible through. */ private var _yume:Object; /** * The actual visual component the YuMe video will be loaded into. */ private var _yume_mc:MovieClip = new MovieClip(); /** * True if the yume swfs have been successfully loaded and initialized, false otherwise. */ private var _yume_ready:Boolean = false; /** * True if there has been a request to play a YuMe ad. This allows the containing application * to initialize and immediately request ad playback without having to worry about asynchronous * load times and have the ad begin playing as soon as it's loaded. */ private var _start_requested:Boolean = false; /** * Your YuMe API key, given to you by YuMe when you're approved. Below is the test key. */ private var _yume_api_key:String = "88ElOFHxAh"; /** * Any tags associated with the current content being monetized. */ private var _tags:String = ""; /** * The title of the current content being monetized. */ private var _title:String = ""; /** * The duration of the current content being monetized. */ private var _duration:Number = 30; /** * The name of the YuMe channel you want to create for advertisers to target. */ private var _channel:String = "your-company"; public var yume_params:Object = { parent_mc: _yume_mc, ad_type: BEFORE_CONTENT, yume_url: yume_player_swf_url, yume_library_swf_url: yume_library_swf_url, // this is the test playlist from the YuMe setup documentation playlist: "http://shadow01.yumenetworks.com/dynamic_preroll_playlist.fmil?domain=88ElOFHxAh", update_interval: 1000, ad_volume: 100, normalscreen_x: 0, normalscreen_y: 0, normalscreen_width: ad_width, normalscreen_height: ad_height, fullscreen_x: 0, fullscreen_y: 0, fullscreen_width: ad_width, fullscreen_height: ad_height, yume_leader_slot: true, css_url: yume_css_url }; private function regeneratePlaylistURL():void { // set playlist url var playlist_type:String = ""; if( yume_params.ad_type == "during_content" ){ playlist_type = "dynamic_midroll_playlist.fmil"; }else if( yume_params.ad_type == "after_content" ){ playlist_type = "dynamic_postroll_playlist.fmil"; }else{ playlist_type = "dynamic_preroll_playlist.fmil"; } yume_params.playlist = "http://shadow01.yumenetworks.com/" + playlist_type + "?channel=" + encodeURIComponent( _channel ) + "&domain=" + _yume_api_key + "&title=" + encodeURIComponent( _title ) + "&tags=" + encodeURIComponent( _tags ) + "&duration=" + _duration; } private function init():void { Security.allowDomain( "*" ); Security.allowInsecureDomain( "*" ); yume_holder.addChild( _yume_mc ); var loader:SWFLoader = new SWFLoader(); loader.addEventListener( Event.COMPLETE, handleLoaderComplete ); loader.load( yume_library_swf_url ); } private function handleLoaderComplete( e:Event ):void { _yume = e.target.content.yume_ad; setupListeners( _yume as IEventDispatcher ); _yume_ready = true; if( _start_requested ){ startAd(); } } public function startAd():void { if( _yume_ready ){ yume_params.normalscreen_width = this.width; yume_params.normalscreen_height = this.height; _yume.start_ad( yume_params ); _start_requested = false; }else{ _start_requested = true; } } // ================= YUME AD LISTENER IMPLEMENTATION ============== private function setupListeners( target:IEventDispatcher ):void { target.addEventListener( "ad_present", ad_present ); target.addEventListener( "ad_absent", ad_absent ); target.addEventListener( "ad_playing", ad_playing ); target.addEventListener( "ad_completed", ad_completed ); target.addEventListener( "ad_closed", ad_closed ); target.addEventListener( "image_closed", image_closed ); target.addEventListener( "ad_reopened", ad_reopened ); target.addEventListener( "ad_clicked2site", ad_clicked2site ); target.addEventListener( "ad_clicked2video", ad_clicked2video ); target.addEventListener( "pip_video_playing", pip_video_playing ); target.addEventListener( "pip_video_completed", pip_video_completed ); target.addEventListener( "ad_error", ad_error ); } private function ad_absent( e:Event ):void { // bubble dispatchEvent( e ); } private function ad_error( e:Event ):void { // bubble dispatchEvent( e ); } private function ad_completed( e:Event ):void { // bubble dispatchEvent( e ); } private function ad_playing( e:Event ):void { trace( e ); // displays the number of seconds left in the ad for the user if it can be determined if( _yume.get_duration() > 0 ){ var seconds_left:int = Math.round( _yume.get_duration() - _yume.get_time() ); info.text = "Sponsor: " + ( seconds_left >= 0 ? seconds_left : '' ) + " second" + ( seconds_left != 1 ? 's' : '' ) + " remaining"; } } private function ad_present( e:Event ):void { trace( e ); } private function image_closed( e:Event ):void { trace( e ); } private function ad_closed( e:Event ):void { trace( e ); } private function ad_reopened( e:Event ):void { trace( e ); } private function ad_clicked2site( e:Event ):void { trace( e ); } private function ad_clicked2video( e:Event ):void { trace( e ); } private function pip_video_playing( e:Event ):void { trace( e ); } private function pip_video_completed( e:Event ):void { trace( e ); } ]]> </mx:Script> <!-- just a holder for the video ad --> <mx:UIComponent id="yume_holder" width="100%" height="100%" creationComplete="init()"/> <!-- displays the seconds countdown --> <mx:HBox backgroundColor="#323232" backgroundAlpha="0.5" width="100%" top="0" paddingLeft="10"> <mx:Label id="info" fontSize="10" color="#FFFFFF"/> </mx:HBox> </mx:Canvas> |
Edit [2010/01/05]
If you are monetizing live streaming content and plan to play midrolls you can add a playlist_midroll property to the YuMe parameters to pre-fetch midrolls:
yume_params.playlist_midroll = "http://shadow01.yumenetworks.com/dynamic_midroll_playlist.fmil" + "?domain=" + _yume_api_key + "&title=" + encodeURIComponent( _title ) + "&tags=" + encodeURIComponent( _tags ) + "&duration=" + _duration;
Then when you’re ready to play a midroll set the ad_type to DURING_CONTENT and call startAd() again to immediately play the cached midroll advertisement.
Gotchas
I only need in-stream video playback and don’t support accompanying banners so I only bubble “ad_completed”, “ad_absent” and “ad_error” events, catching any other YuMe events you are interested in should be pretty self-explanatory.
Also, YuMe notes that there are some ad units that do not broadcast their duration. An example of this might be a preroll that a user can interact with (pause, switch ad flv’s, etc). Because of this, they recommend that instead of a countdown timer, customers display a message like “Your content will resume after this advertisement.”
If you are running the example via the Flash/Flex Builder IDE and YuMe happens to send you back a powerroll to display you might see a variety of Security Errors thrown, according to YuMe support this apparently doesn’t happen in production.
Special thanks to David Lea of YuMe support for his quick, clear responses.
January 22, 2010 at 1:24 am
Saying that trying to do anything useful with the YuMe SDK documentation is a wild goose hunt is like saying that getting your face mauled off by a bear is a minor cosmetic issue.
The problem isn’t simply isolated to the documentation, either. There’s also the matter of the completely inconsistent and un-ActionScript-like coding conventions, the broken normalscreen_x and normalscreen_y attributes, the lack of an swc (which means no debug symbols, no compiling it in using the Flash IDE, and extra function calls to get information that should be part of the dispatched events)…the list goes on.
I’ve written a more complete wrapper to abstract away most of the stupidity of the YuMe API and plan to release it soon. In the meantime, I hope that they fire their entire development staff, because this SDK is a joke.
Regards,
February 9, 2010 at 4:18 pm
Hi Ian,
Released my wrapper & documentation today. Check it out and send over some feedback if you have time. YuMeWrapper.
Regards,
February 15, 2010 at 5:06 pm
Great stuff, thanks! but I need a stop() or unload() function to stop the ad in the middle of play.