This mini-tutorial is part of the SLHVP documentation
One of the goals of the Silverlight HyperVideo Project is to demonstrate best practices. So when is it okay to throw in a quick hack to get things working?
Case in point: as I approached the release this week for the latest alpha, I noted that the player was not updating when the user clicked on a topic, though the links were appearing as if the player had been advanced.
To make the issue clear, in this image you see a cut down version of the player. Each item (video) has its own set of topics and links. The topics are shown as soon as the video starts, the links appear as the video plays.
If the user clicks on a topic (lower left) what should happen is that the video (middle) jumps to that topic and the scrubber (below the video) should advance to indicate where in the video the topic begins, relative to the start of the video.
(Click on image to see it full size)
After a bit of debugging (and some frantic calls to the Vertigo folks who wrote the Silverlight Media Framework, we were able to see that the problem was in the binding:
<SmoothPlayer:Player Style="{StaticResource SLHVPTemplate}" x:Name="smoothPlayer"> <hvp:HVPCoreMediaElement x:Name="corePlayer" SmoothStreamingSource="{Binding URI, Mode=TwoWay}" AutoPlay="True" Position="{Binding Offset, Mode=TwoWay}" /> </SmoothPlayer:Player>
Checking the source code for the SMF (available here) we found that Position is a dependency property and was not responding to the Binding as we expected.
Since the folks at Vertigo owned that code and were convinced they could fix this in the next release, I opted to take their change an copy it into my project. To encapsulate the hack, however, I created the HVPCoreMediaElement, derived from the CoreSmoothStreamingMediaElement.
C#
using System; using System.Windows; using Microsoft.SilverlightMediaFramework.Player; namespace SilverlightHVP.View { public class HVPCoreMediaElement : CoreSmoothStreamingMediaElement { public TimeSpan PositionOverride { get { return ( TimeSpan ) GetValue( PositionOverrideProperty ); } set { SetValue( PositionOverrideProperty, value ); } } public static readonly DependencyProperty PositionOverrideProperty = DependencyProperty.Register( "PositionOverride", typeof( TimeSpan ), typeof( CoreSmoothStreamingMediaElement ), new PropertyMetadata( HVPCoreMediaElement.OnPositionOverridePropertyChanged ) ); private static void OnPositionOverridePropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) { HVPCoreMediaElement source = d as HVPCoreMediaElement; source.OnPositionOverrideChanged(); } private void OnPositionOverrideChanged() { // Hack!!, to databind to Position Position = PositionOverride; } } }
VB.Net
Imports System Imports System.Windows Imports Microsoft.SilverlightMediaFramework.Player Namespace SilverlightHVP.View Public Class HVPCoreMediaElement Inherits CoreSmoothStreamingMediaElement Public Property PositionOverride() As TimeSpan Get Return CType(GetValue(PositionOverrideProperty), TimeSpan) End Get Set(ByVal value As TimeSpan) SetValue(PositionOverrideProperty, value) End Set End Property Public Shared ReadOnly PositionOverrideProperty As DependencyProperty = DependencyProperty.Register("PositionOverride", GetType(TimeSpan), GetType(CoreSmoothStreamingMediaElement), New PropertyMetadata(AddressOf HVPCoreMediaElement.OnPositionOverridePropertyChanged)) Private Shared Sub OnPositionOverridePropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) Dim source As HVPCoreMediaElement = TryCast(d, HVPCoreMediaElement) source.OnPositionOverrideChanged() End Sub Private Sub OnPositionOverrideChanged() ' Hack!!, to databind to Position Position = PositionOverride End Sub End Class End Namespace
I could then modify the binding
<SmoothPlayer:Player Style="{StaticResource SLHVPTemplate}" x:Name="smoothPlayer" > <hvp:HVPCoreMediaElement x:Name="corePlayer" SmoothStreamingSource="{Binding URI, Mode=TwoWay}" AutoPlay="True" PositionOverride="{Binding Offset, Mode=TwoWay}" </SmoothPlayer:Player>
And all was right with the world. This was minimally intrusive, and I was very pleased with it. I should have known.
In For A Penny…
The problem, of course, is that now that I’m binding the new PositionOverride I’m reading wonderfully, but writing, not so. There are a lot of ways to fix this, but the easiest, fastest and perhaps ugliest is to add a second binding. After all, binding to Position was working great for setting, and PositionOverride is now working great for reading.
<SmoothPlayer:Player Style="{StaticResource SLHVPTemplate}" x:Name="smoothPlayer" > <hvp:HVPCoreMediaElement x:Name="corePlayer" SmoothStreamingSource="{Binding URI, Mode=TwoWay}" AutoPlay="True" PositionOverride="{Binding Offset}" Position="{Binding PlayerPosition, Mode=TwoWay}" /> </SmoothPlayer:Player>
If this were the only change, maybe okay, but unfortunately we have to hack the VM as well,
C#
public TimeSpan Offset { get { return state.Position; } } public TimeSpan PlayerPosition { get { return state.Position; } set { if ( value > previousPosition + Interval ) { previousPosition = value; state.Position = value; } } }
VB.Net
Public ReadOnly Property Offset() As TimeSpan Get Return state.Position End Get End Property Public Property PlayerPosition() As TimeSpan Get Return state.Position End Get Set(ByVal value As TimeSpan) If value > previousPosition + Interval Then previousPosition = value state.Position = value End If End Set End Property
Oh What A Tangled Web We Weave…
This is so ugly, and so likely to end up being a headache when we are ready to undo it, that I’m very tempted to find a cleaner solution. On the other hand, I know that we’re actively working on SMF version 2, and that even before that I may have an interim release that makes the whole problem go away, so my Faustian bargain is to comment the hack
/* HACK!! We are binding the getter of Offset to the Position of the CoreSmoothStreamingMediaElement but we are binding the setter to the PositionOverride property of the (temporary) class HVPCoreMediaElement that derives from CoreSmoothStreamingMediaElement. Also note that this is two way binding and we need both the getter and the setter. To Fix: 1. Combine move get and set from PlayerPosition to Offset and remove PlayerPosition 2. Change Binding in SmoothStreamingPlayer to bind to Offset, two way 3. Remove file (and class) HVPCoreMediaElement.cs */ public TimeSpan Offset { get { return state.Position; } } public TimeSpan PlayerPosition { get { return state.Position; } set { if ( value > previousPosition + Interval ) { previousPosition = value; state.Position = value; } } }
VB.NET
'HACK!! We are binding the getter of Offset to the 'Position of the CoreSmoothStreamingMediaElement 'but we are binding the setter to the PositionOverride 'property of the (temporary) class HVPCoreMediaElement 'that derives from CoreSmoothStreamingMediaElement. 'Also note that this is two way binding and we 'need both the getter and the setter. 'To Fix: '1. Combine move get and set from PlayerPosition to ' Offset and remove PlayerPosition '2. Change Binding in SmoothStreamingPlayer to bind to ' Offset, two way '3. Remove file (and class) HVPCoreMediaElement.vb Public ReadOnly Property Offset() As TimeSpan Get Return state.Position End Get End Property Public Property PlayerPosition() As TimeSpan Get Return state.Position End Get Set(ByVal value As TimeSpan) If value > previousPosition + Interval Then previousPosition = value state.Position = value End If End Set End Property
Let the Flame Wars Begin
“Well?,” as Howie Mandel’s tiny alter-ego Bobby asks, “what would you say?”
2 Responses to When Is It OK To Hack?