When Is It OK To Hack?

MiniTutorialLogo

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?

CutDownPlayerCase 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?”

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Patterns & Skills and tagged , . Bookmark the permalink.

2 Responses to When Is It OK To Hack?

Comments are closed.