A BEML Brightcove Player with a Flex Search Widget
Posted by: Bob de Wit in Brightcove, FlexThis article will detail how to develop and plug in a search widget into a Brightcove Player using BEML. I’ve created the search widget in Flex, since the markup is very similar to BEML. The article and sample code have been updated to show filtering of the search results based on a playlist selection.
Preparation
Before creating your own widgets to plug into a Brightcove Player using BEML, make sure you read the introduction help pages to ensure you are clear on the required preparation steps. We will be using the Media API, so that you can search for videos throughout your entire account. Also make sure you have a read token handy.
Creating The Pieces
There are three main pieces to create for a search widget: the repeating display logic for the results, the widget that needs to receive a reference from the Brightcove Player it’s been plugged into, and the BEML template that actually plugs in the widget.
The Result List Component
To display the results, we’ll use a simple repeater in Flex, like this:
<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" horizontalScrollPolicy="off" xmlns:ns1="*"> <mx:Script> <![CDATA[ import mx.formatters.NumberBase; import mx.collections.ArrayCollection; import mx.rpc.events.ResultEvent; import com.adobe.serialization.json.JSON; import mx.controls.Alert; import mx.managers.CursorManager; import mx.core.Application; public function SetDataProvider( dp:Object ):void { tileRepeater.dataProvider = dp; } private function SelectVideo(video:Object):void { //Alert.show( "Video ID Selected: " + video.id ); Application.application.selectVideo(video); } ]]> </mx:Script> <mx:VBox x="0" y="0" width="100%" height="100%" horizontalScrollPolicy="off" borderStyle="none" cornerRadius="8"> <mx:Repeater id="tileRepeater"> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="96" horizontalScrollPolicy="off" borderStyle="none" cornerRadius="20"> <mx:Label text="{tileRepeater.currentItem.id}" id="videoID" visible="false"/> <mx:HBox x="0" y="0" width="100%" height="100%" cornerRadius="8" borderStyle="solid" horizontalGap="4" paddingLeft="4" paddingTop="2"> <mx:Image source="{tileRepeater.currentItem.thumbnailURL}" width="120" height="90" useHandCursor="true" click="{SelectVideo(event.currentTarget.getRepeaterItem())}"> <mx:rollOverEffect> <mx:Glow color="#0000ff" blurXTo="5" blurYTo="5" /> </mx:rollOverEffect> </mx:Image> <mx:VBox> <mx:LinkButton label="{tileRepeater.currentItem.name}" fontWeight="bold" fontSize="10" click="{SelectVideo(event.currentTarget.getRepeaterItem())}" width="120" textAlign="left"/> <mx:Text id="Description" text="{tileRepeater.currentItem.shortDescription}" width="120" height="62"/> </mx:VBox> </mx:HBox> </mx:Canvas> </mx:Repeater> </mx:VBox> </mx:Canvas>
As for the widget itself, here is the complete code listing (using the list repeater we just defined above). I’ve added functions and handlers to loop the results from the search function to be correlated with a playlist id, selected from a combobox, as well as the initialization code to populate the combobox.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="300" layout="absolute" xmlns:ns1="*" initialize="InitApp()" backgroundColor="#CCCCCC" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#FFFFFF, #CCCCCC]"> <mx:Script> <![CDATA[ import mx.formatters.NumberBase; import mx.managers.CursorManager; import mx.collections.ArrayCollection; import mx.rpc.events.ResultEvent; import com.adobe.serialization.json.JSON; import mx.controls.Alert; //Replace with your own read token private var ReadToken: String = "Lkn9dGUsCgNfRD8cKipEmuLcr-Oo3u8wV7hdHV945Fw."; //Brightcove Service URL private var ServiceURL: String = "http://api.brightcove.com/services/library"; //Placeholder for BC Player Reference private var BCPlayer: Object = null; //Placeholder for the result dataprovider private var Results: ArrayCollection; //Initialize App private function InitApp():void { //Initialize by getting all playlists in the account GetPlayLists(); } //BEML Widget function to receive reference to BC Experience public function setInterface(player:Object):void { BCPlayer = player; } public function selectVideo(video:Object):void { if (BCPlayer != null) { var Content:Object = BCPlayer.getModule("content"); var Player:Object = BCPlayer.getModule("videoPlayer"); //Content.getVideoAsynch(video.id); Player.loadVideo(video.id ); } else { Alert.show("You have selected video " + video.id + ", but you are not running this widget from within a Brightcove Player" ); } } private function GetPlayLists():void { var request:URLRequest = new URLRequest(ServiceURL); var loader:URLLoader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.TEXT; request.method = URLRequestMethod.GET; //Set Busy Cursor CursorManager.setBusyCursor(); //Define the REST API variables to be passed var variables:URLVariables = new URLVariables(); variables.token = ReadToken; variables.command = "find_all_playlists"; variables.fields = "id,name"; // assign the data to be sent by GET request.data = variables; // add event listener loader.addEventListener(Event.COMPLETE, handlePlayListsResults); loader.load(request); } private function handlePlayListsResults(evt:Event):void { //Remove Busy Cursor CursorManager.removeBusyCursor(); //Get the returned JSON data string var response:String = evt.target.data as String; //Uncomment this if you want to see the raw response string Alert.show(response); //The list of returned videos is embedded in the "items" property //of the root JSON object, so we will decode to a container object var container:Object = (JSON.decode(response) as Object); //create a new ArrayCollection passing the de-serialized Array //ArrayCollections work better as DataProviders, as they can //be watched for changes. var dp:ArrayCollection = new ArrayCollection(container.items); //Add an "all playlist" option to the dataprovider dp.addItemAt( { "id" : 0, "name" : "Search All Playlists" }, 0 ); //pass the ArrayCollection to the VideoList as its dataProvider. PlayLists.dataProvider = dp; PlayLists.labelField = "name"; } private function ButtonCallAPIClick(): void { var request:URLRequest = new URLRequest(ServiceURL); var loader:URLLoader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.TEXT; request.method = URLRequestMethod.GET; //Set Busy Cursor CursorManager.setBusyCursor(); //Define the REST API variables to be passed var variables:URLVariables = new URLVariables(); // First, Add your read token and the search text - if any variables.token = ReadToken; if( SearchTerm.text != "" ) { variables.text = SearchTerm.text; variables.command = "find_videos_by_text"; } else { variables.command = "find_all_videos"; } // assign the data to be sent by GET request.data = variables; // add event listener loader.addEventListener(Event.COMPLETE, handleResults); loader.load(request); } private function handleResults(evt:Event):void { //Remove Busy Cursor CursorManager.removeBusyCursor(); //Get the returned JSON data string var response:String = evt.target.data as String; //Uncomment this if you want to see the raw response string //Alert.show(response); //The list of returned videos is embedded in the "items" property //of the root JSON object, so we will decode to a container object var container:Object = (JSON.decode(response) as Object); //create a new ArrayCollection passing the de-serialized Array //ArrayCollections work better as DataProviders, as they can //be watched for changes. var dp:ArrayCollection = new ArrayCollection(container.items); //Convert the UNIX date into an AS3 Date for(var i:int = 0; i< dp.length; i++) { var video:Object = dp.getItemAt(i); var n:Number = video.publishedDate; video.publishedDate = new Date(n); } Results = dp; //Filter the results based on the playlist selection //in the combobox. If All Playlists, then id = 0 if ( PlayLists.selectedItem.id == 0 ) { //All Playlists option selected, so we can just //pass the ArrayCollection to the VideoList as its dataProvider. videoList.SetDataProvider(Results); } else { FilterResults(); } } private function FilterResults():void { var request:URLRequest = new URLRequest(ServiceURL); var loader:URLLoader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.TEXT; request.method = URLRequestMethod.GET; //Set Busy Cursor CursorManager.setBusyCursor(); //Define the REST API variables to be passed var variables:URLVariables = new URLVariables(); variables.token = ReadToken; variables.command = "find_playlist_by_id"; variables.playlist_id = PlayLists.selectedItem.id; variables.fields = "videoIds"; // assign the data to be sent by GET request.data = variables; // add event listener loader.addEventListener(Event.COMPLETE, handleFilterResults); loader.load(request); } private function handleFilterResults(evt:Event):void { //Remove Busy Cursor CursorManager.removeBusyCursor(); //Get the returned JSON data string var response:String = evt.target.data as String; //Uncomment this if you want to see the raw response string Alert.show(response); //The list of returned videos is embedded in the "items" property //of the root JSON object, so we will decode to a container object var container:Object = (JSON.decode(response) as Object); //create a new ArrayCollection passing the de-serialized Array //ArrayCollections work better as DataProviders, as they can //be watched for changes. var dp:ArrayCollection = new ArrayCollection(container.videoIds); var found:Boolean = false; for( var i:int = Results.length -1; i >= 0; i-- ) { var video: Object = Results.getItemAt(i); found = false; for( var j:int = 0; j < dp.length; j++ ) { var plitem: Object = dp.getItemAt(j); if( video.id == plitem ) { found = true; } } if( ! found ) { Results.removeItemAt(i); } } videoList.SetDataProvider(Results); } ]]> </mx:Script> <mx:VBox left="10" top="10" right="10" bottom="10"> <mx:HBox> <mx:TextInput id="SearchTerm" width="196" text="warcraft"/> <mx:Button label="Search" id="ButtonCallAPI" click="ButtonCallAPIClick();" width="73"/> </mx:HBox> <mx:HBox> <mx:ComboBox width="274" id="PlayLists" ></mx:ComboBox> </mx:HBox> <ns1:VideoList id="videoList"> </ns1:VideoList> </mx:VBox> </mx:Application>
To plug this widget into a BEML template, first make sure you have a cross-domain policy file in place in the root folder where you will place the compiled swf online, for example like this:
<cross-domain-policy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/PolicyFile.xsd"> <allow-access-from domain="*"/> </cross-domain-policy>
Then, create a new BEML template with the following markup:
<Runtime> <Theme name="Deluxe" style="Light"/> <Layout> <VBox padding="3"> <HBox padding="3"> <VideoPlayer id="videoPlayer"/> <SWFLoader width="300" height="412" source="http://active6.com/blog/code/beml_search/Brightcove_BEML_SearchWidget.swf" depth="2"/> </HBox> </VBox> </Layout> </Runtime>
All that remains to do now is to create a player based on this template and publish it (make sure to enable the API access in the player’s configuration). Enjoy!

Entries (RSS)