I finished building my first 100% pure Silverlight 1.0 application, the result of which can be viewed at http://www.thejamesbondmovies.com, and
I wanted to share a few of the techniques I learned. Anyone who has played with Microsoft Expression Encoder will probably recognize the player as coming from one of the built in templates. Expanding the
XAML to include my own elements was not too difficult, the Blend 2 September Preview made that quite easy. When I have more time I will describe in detail how I made the scrollable playlist as I could find no tutorials anywhere on how to do that, but for right now I just want to address the basics of Scripting the output. I am by no means a JavaScript Guru, which is perhaps why I struggled a bit to understand exactly how to interact with my new elements, and exactly where to put my code. When you build a new Silverlight application in Blend 2 or Visual Studio the JavaScript that is provided to you as a starting point
appears to be quite different to that outputted by the encoder. Visual Studio gives you the following in a file called (by default) Scene.xaml.js:
if (!window.SilverlightJSApplication1)
window.SilverlightJSApplication1 = {};
SilverlightJSApplication1.Scene = function()
{
}
SilverlightJSApplication1.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
// Sample button event hookup: Find the button and then attach event handlers
this.button = rootElement.children.getItem(0);
this.button.addEventListener("MouseEnter", Silverlight.createDelegate(this, this.handleMouseEnter));
this.button.addEventListener("MouseLeftButtonDown", Silverlight.createDelegate(this, this.handleMouseDown));
this.button.addEventListener("MouseLeftButtonUp", Silverlight.createDelegate(this, this.handleMouseUp));
this.button.addEventListener("MouseLeave", Silverlight.createDelegate(this, this.handleMouseLeave));
},
// Sample event handlers
handleMouseEnter: function(sender, eventArgs)
{
// The following code shows how to find an element by name and call a method on it.
var mouseEnterAnimation = sender.findName("mouseEnter");
mouseEnterAnimation.begin();
},
handleMouseDown: function(sender, eventArgs)
{
var mouseDownAnimation = sender.findName("mouseDown");
mouseDownAnimation.begin();
},
handleMouseUp: function(sender, eventArgs)
{
var mouseUpAnimation = sender.findName("mouseUp");
mouseUpAnimation.begin();
// Put clicked logic here
alert("clicked");
},
handleMouseLeave: function(sender, eventArgs)
{
var mouseLeaveAnimation = sender.findName("mouseLeave");
mouseLeaveAnimation.begin();
}
}
Blend 2 gives you an almost identical file called Page.xaml.js:
if (!window.MyProjName)
window.MyProjName = {};
MyProjName.Page = function()
{
}
MyProjName.Page.prototype =
{
handleLoad: function(control, userContext, rootElement)
{
this.control = control;
// Sample event hookup:
rootElement.addEventListener("MouseLeftButtonDown", Silverlight.createDelegate(this, this.handleMouseDown));
},
// Sample event handler
handleMouseDown: function(sender, eventArgs)
{
// The following line of code shows how to find an element by name and call a method on it.
// this.control.content.findName("Timeline1").Begin();
}
}
Both of these files make it very easy to pick up your own named elements, and add event handlers to them because the handleLoad function passes the
plugIn (visual Studio) or control (Blend 2) argument that can be used to easily find a reference to your own XAML element:
control.content.findName("MyXamlElement"); //or plugIn.content.findName("MyXamlElement");
But having played with that model a few times and become comfortable with it, you then navigate to your Expression encoder's output location and find as many as
six JavaScript files. Obviously, Silverlight.js and MicrosoftAjax.js aren't what you're looking for, PlayerStrings.js is pretty empty, and BasePlayer.js clearly wasn't designed for easy editing as it has been compressed. So that just leaves player.js and StartPlayer.js. I'll come back to player.js in a moment,
for it is StartPlayer.js that is the Expression Encoder's equivalent to Scene.xaml.js.
function get_mediainfo(mediainfoIndex) {
switch (mediainfoIndex) {
case 0:
return { "mediaUrl": "MyVideo.wmv",
"placeholderImage": "",
"chapters": [
] };
default:
throw Error.invalidOperation("No such mediainfo");
}
}
function StartWithParent(parentId, appId) {
new StartPlayer_0(parentId);
}
function StartPlayer_0(parentId) {
this._hostname = EePlayer.Player._getUniqueName("xamlHost");
Silverlight.createObjectEx( { source: 'player.xaml',
parentElement: $get(parentId ||"divPlayer_0"),
id:this._hostname,
properties:{ width:'100%', height:'100%', version:'1.0', background:document.body.style.backgroundColor, isWindowless:'false' },
events:{ onLoad:Function.createDelegate(this, this._handleLoad) } } );
this._currentMediainfo = 0;
}
StartPlayer_0.prototype= {
_handleLoad: function() {
this._player = $create( ExtendedPlayer.Player,
{ // properties
autoPlay : true,
volume : 1.0,
muted : false
},
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname) );
this._playNextVideo();
},
_onMediaEnded: function(sender, eventArgs) {
window.setTimeout( Function.createDelegate(this, this._playNextVideo), 1000);
},
_onMediaFailed: function(sender, eventArgs) {
alert(String.format( Ee.UI.Xaml.Media.Res.mediaFailed, this._player.get_mediaUrl() ) );
},
_playNextVideo: function() {
var cVideos = 1;
if (this._currentMediainfo<cVideos)
this._player.set_mediainfo( get_mediainfo( this._currentMediainfo++ ) );
}
}
The first thing I noticed was that unlike the Visual Studio output, the _handleLoad function from Expression
Encoder does not pass in a control or PlugIn reference, so how do you get one? Well the way I did it (and I could find no documentation anywhere
on how to do this) was simply to add my own (new code is bold):
StartPlayer_0.prototype= {
_handleLoad: function(control) {
this._player = $create( ExtendedPlayer.Player,
{ // properties
autoPlay : true,
volume : 1.0,
muted : false
},
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname) );
control.Content.findName("MyXamlElement");
this._playNextVideo();
},
That's all there is to it. Now go ahead and add your events as before. But wait, so what is player.js for then? Glad you asked. Player.js allows you to override the code in the BasePlayer.js file that
so clearly was not meant for editing. For example, suppose you had created an animation in your
XAML that made the video screen appear, perhaps from behind theatre style curtains that parted, or from behind some other element. You could override the
play() function in BasePlayer.js to play your animation before the video:
Type.registerNamespace('ExtendedPlayer');
ExtendedPlayer.Player = function(domElement) {
ExtendedPlayer.Player.initializeBase(this, [domElement]);
}
ExtendedPlayer.Player.prototype = {
play: function() {
this.get_element().content.findName('OpenCurtains').begin();
ExtendedPlayer.Player.callBaseMethod(this, 'play');
},
stop: function() {
this.get_element().content.findName('CloseCurtains').begin();
ExtendedPlayer.Player.callBaseMethod(this, 'stop');
},
pause: function() {
alert('You clicked Pause');
ExtendedPlayer.Player.callBaseMethod(this, 'pause');
}
}
ExtendedPlayer.Player.registerClass('ExtendedPlayer.Player', EePlayer.Player);