September 2009 Entries

Pouring from drunktender

One of my big things I love about drunktender is everything is off the shelf.  There are a few bits I’ve never really been happy with but lived with them.  I was recently told to design a better spout for the Microsoft PDC conference so this is what I came up with.  I did a quick test with what I have here and I easily put 20 1/4” tubes though the 2” pipe.  The white PVC is from the plumbing department and the grey PVC is from the electrical department.  I have an exploded and a loose connected version below of what I was planning pre-painting.

Possible spout for drunktender Possible spout for drunktender

I’ll drill a hole though the table and then use a flange which will be hidden on the underside to securely mount it to the table.

6102438[1]

Why it is a bad idea to prank me

So a coworker decided it would be a good idea to hide both my rental keys and my car keys the Friday before Labor Day.  Being the good little mindless drone I was, I stayed late but didn’t realize my keys were gone until 9PM.  Now I’m all for pranks and someone getting me good but keys, wallets, purses are off limits since that person thinks someone stole the keys / wallet / purse.

Needless to say, I was irked and said on twitter “the prank wars, started they have”.  And this weekend, I got my revenge.  Just remember, I could have been far meaner Nic.

Foam Chair Prank

Foam Chair PrankFoam Chair PrankFoam Chair Prank
Foam Chair PrankFoam Chair PrankFoam Chair Prank

Get Microsoft Silverlight

Creating my own media player

I’ve been tasked at creating a new feature / porting an application from WinForms to WPF at work.  Since Channel9 deals heavily with video, you guessed it, I have to deal with video!

After attempting to see if there was a decent WPF player or example for the MediaElement, it really didn’t seem like there was.  Some of the examples did some crazy stuff when it wasn’t needed.

So here are my requirements for it as of now:

  • Play, Stop, Pause
  • Be able to see current position’s time code
  • Seek by holding down the slider and by clicking
  • Adjust Volume
  • Adjust Speed of playback
  • Adjust video position frame by frame
  • Seeing the current position

From a quick and dirty UI, this is what I came up with.  The 3 icons are from the Silk set by FamFamFam.
image

XAML:

<Grid Background="Black" >
	<Grid.RowDefinitions>
		<RowDefinition Height="*" />
		<RowDefinition Height="60" />
	</Grid.RowDefinitions>


	<MediaElement Grid.Row="0" Source="media\theoffice.wmv" Name="videoElement" 
				  LoadedBehavior="Manual" UnloadedBehavior="Stop" ScrubbingEnabled="true"
				  MediaOpened="Element_MediaOpened" MediaEnded="Element_MediaEnded" Loaded="videoElement_Loaded" />
	<StackPanel Grid.Row="1">
		<StackPanel Orientation="Horizontal">
			<TextBlock Foreground="White" Margin="5"  VerticalAlignment="Center">Seek To</TextBlock>
			<Slider Name="timelineSlider" Width="361" SmallChange="1" LargeChange="10000" 
					PreviewMouseUp="timelineSlider_MouseUp"  />
			<TextBlock Foreground="White" Margin="5"  VerticalAlignment="Center" Name="videoElementTime" >##:##:##:##</TextBlock>
		</StackPanel>
		<StackPanel HorizontalAlignment="Center" Width="450" Orientation="Horizontal">
			<Image Source="..\images\control_play.png" MouseDown="OnMouseDownPlayMedia" Margin="5" Name="test" />
			<Image Source="..\images\control_pause.png" MouseDown="OnMouseDownPauseMedia" Margin="5" />
			<Image Source="..\images\control_stop.png" MouseDown="OnMouseDownStopMedia" Margin="5" />
			<TextBlock Foreground="White" VerticalAlignment="Center" Margin="5">Volume</TextBlock>
			<Slider Name="volumeSlider" VerticalAlignment="Center" 
					ValueChanged="ChangeMediaVolume" Minimum="0" Maximum="1" Value="0.5" Width="70" IsSnapToTickEnabled="True" TickFrequency="0.1" />
			<TextBlock Foreground="White" Margin="5,5,30,0" VerticalAlignment="Center" Text="{Binding ElementName=volumeSlider, Path=Value}"/>

			<!-- Volume slider. This slider allows a Volume range between 0 and 10. -->
			<TextBlock Foreground="White" Margin="5"  VerticalAlignment="Center">Speed</TextBlock>
			<Slider Name="speedRatioSlider" VerticalAlignment="Center" 
					ValueChanged="ChangeMediaSpeedRatio" Value="1" Width="70" SmallChange=".5" IsSnapToTickEnabled="True" Maximum="4" LargeChange="0.5" TickFrequency="0.5" />
			<TextBlock Foreground="White" Margin="5" VerticalAlignment="Center" Text="{Binding ElementName=speedRatioSlider, Path=Value}"/>
		</StackPanel>
	</StackPanel>
</Grid>

Lets go through my requirements 1 by 1 for source code:

Play, Stop, Pause

This is very straight forward.

videoElement.Play();
videoElement.Stop();
videoElement.Pause();

Seek by holding down the slider and by clicking

Once again, pretty easy.  I’m using milliseconds for calculating everything.  I saw someone used

// in Element_MediaOpened
timelineSlider.Maximum = videoElement.NaturalDuration.TimeSpan.TotalMilliseconds;

// in timelineSlider_MouseUp
videoElement.Position = new TimeSpan(0, 0, 0, 0, (int)timelineSlider.Value);

Adjust Volume and speed of playback

Pretty easy as well

videoElement.Volume = volumeSlider.Value;
videoElement.SpeedRatio = speedRatioSlider.Value;

Adjust video position frame by frame

This is where stuff starts getting a bit interesting due to focus.  I want to use the arrow keys but if a slider has focus, the arrow keys will alter that element plus this.  This is the hack I came up with.

Since this is a user control, you need to get the window dynamically, by using Window.GetWindow(this), you can get it.  You attach an event to the PreviewKeyDown event.  From here now I can see the fact a key is getting used and adjust the focus back onto slider.  Since the slider has zero binding to video position, its position will be adjusted by UpdateUserInterfaceByTimer()!

private void videoElement_Loaded(object sender, RoutedEventArgs e)
{
	var window = Window.GetWindow(this);

	if (window != null)
		window.PreviewKeyDown += VideoElementPreviewKeyDown;
}

private readonly TimeSpan _timeSpanTick = new TimeSpan(0, 0, 0, 0, (int)((1.0 / 29.97) * 1000)); // time for 1 HD frame
private void VideoElementPreviewKeyDown(object sender, KeyEventArgs e)
{
	if (e.Key != Key.Right && e.Key != Key.Left)
		return;

	if (!timelineSlider.IsFocused)
		timelineSlider.Focus();

	PauseVideoElement();

	if (e.Key == Key.Left)
		videoElement.Position -= _timeSpanTick;
	else
		videoElement.Position += _timeSpanTick;

	UpdateUserInterfaceByTimer();
}

private void UpdateUserInterfaceByTimer()
{
	videoElementTime.Text = videoElement.Position.ToString();
	if (!timelineSlider.IsFocused)
		timelineSlider.Value = videoElement.Position.TotalMilliseconds;
}

Be able to see the current position’s time code

This may be possible but from what I read, I couldn’t see anyone doing a direct data bind here and everyone used a timer.  I used the UpdateUserInterfaceByTimer() function to then update stuff.  If the video wasn’t playing, I made sure to turn off the timer.  The 100 millisecond update timer was just something I figured seemed like a decent time code.  I doubt anyone will notice it isn’t updating at 60 frames a second.

private readonly DispatcherTimer _positionTimer;

public VideoElement()
{
	InitializeComponent();
	_positionTimer = new DispatcherTimer(new TimeSpan(0, 0, 0, 0, 100), DispatcherPriority.Normal, DispatcherTimerTick, Dispatcher);
}

private void DispatcherTimerTick(object sender, EventArgs e)
{
	UpdateUserInterfaceByTimer();
	CommandManager.InvalidateRequerySuggested();
}

void OnMouseDownPlayMedia(object sender, MouseButtonEventArgs args)
{
	_positionTimer.Start();
	videoElement.Play();
	InitializePropertyValues();
}

void OnMouseDownPauseMedia(object sender, MouseButtonEventArgs args)
{
	PauseVideoElement();
}

// Stop the media.
void OnMouseDownStopMedia(object sender, MouseButtonEventArgs args)
{
	PauseVideoElement();
	videoElement.Stop();
}

void PauseVideoElement()
{
	_positionTimer.Stop();
	videoElement.Pause();
}

And with that, you have a highly functional WPF MediaElement control.  Still can’t do everything that the Silverlight version can, but that is a different discussion…