Tuesday, April 10, 2007

Preview image for MediaElement and more

This article covers two topics. One is how to get over ugly clr-namespace syntax by defining your own namespaces, the other - what is MediaPlayer control and how to create your own MediaElement clone with video preview as most of video hosting services like YouTube do. So let's start

First of all, I want my control looks like a normal one in XAML page. I do not want ugly clr-namespace:myNamespace etc, reference, I WANT URL!!!

<Window x:Class="MediaSense.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:l="http://schemas.sharpsoft.net/xaml"

    Title="MediaSense" Height="600" Width="800"

    >

    <l:PreviewMediaElement Name="video"  Width="640" Height="480" Source="../../Butterfly.wmv" />

</Window>

Don't "xmlns:l="http://schemas.sharpsoft.net/xaml" looks much better? How to do it. Just go to your AssemblyInfo.cs file in the assembly, where your control sits and add namespace mapping. Something like this, will be enough to make your reference looking better

 

[assembly: XmlnsDefinition("http://schemas.sharpsoft.net/xaml", "MediaSense")]

[assembly: XmlnsDefinition("http://schemas.sharpsoft.net/xaml", "MediaSense.Controls")]

Please, don't ask me why this does not works for controls in the assembly, that reference it. I do not know :). The other point is, that this method will not solve warnings like " The element 'StackPanel' in namespace 'http://schemas.microsoft.com/winfx/2006/xaml/presentation' has invalid child element 'PreviewMediaElement' in namespace 'http://schemas.sharpsoft.net/xaml'. List of possible elements expected: 'StackPanel.CanHorizontallyScroll, etc., etc., etc.,"

The other thing that not covered by this, as well as the old method is intellisense and autocomplete. You can do it yourself by adding xsd schema file into C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas. You should create xsd like following in order to get over this problem and recieve intellisense. The only way to do it is by hand, due XSD Generator does not works good enought for XAML maps. Good forces are working on it those days, so it worth to wait

<?xml version="1.0" encoding="utf-8"?>

<xs:schema targetNamespace="http://schemas.sharpsoft.net/xaml"

  elementFormDefault="qualified"

  xmlns="http://schemas.sharpsoft.net/xaml"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  xmlns:xs="http://www.w3.org/2001/XMLSchema"

  >

  <xs:import namespace="http://schemas.sharpsoft.net/xaml" schemaLocation="xaml2006.xsd"/>

  <xs:element name="PreviewMediaElement" type="dPreviewMediaElement" substitutionGroup="xs:dMediaElement">

    <xs:annotation>

      <xs:documentation>Represents a control that contains audio and/or video with preview. </xs:documentation>

    </xs:annotation>

  </xs:element>

  <xs:complexType name="dPreviewMediaElement" >

    <xs:choice minOccurs="0" maxOccurs="unbounded">

      <xs:element name="MediaElement.Source" type="dUriContainer" minOccurs="0" maxOccurs="1">

        <xs:annotation>

          <xs:documentation>Gets or sets a media source on the MediaElement. This is a dependency property.</xs:documentation>

        </xs:annotation>

      </xs:element>

If you are using Orcas bit, you can forget all above, there even custom controls are having intellisese support as well as user controls in current framework 2.0

So far so good. Now, let's cover preview image. In order to draw first (or any other) video frame we have to treat video drawing "by code", so the only class usable for us is MediaPlayer.  After first initialization, we'll subscribe to Video Opened event to get the first frame drawn like this

 

void getFirstPreview()

        {

            RenderTargetBitmap target = new RenderTargetBitmap(_player.NaturalVideoWidth, _player.NaturalVideoHeight, 1 / 100, 1 / 100, PixelFormats.Pbgra32);

            DrawingVisual visual = new DrawingVisual();

            DrawingContext context = visual.RenderOpen();

            context.DrawVideo(_player, frameRect);

            context.Close();

            target.Render(visual);

 

            if (_prev == null)

            {

                _prev = new Image();

            }

 

 

            _prev.Source = BitmapFrame.Create(target).GetAsFrozen() as BitmapFrame;

 

            this.Content = _prev;

            _player.Stop();

            _player.Position = TimeSpan.FromSeconds(0);

        }

As you can see, we just renderred first video frame into Bitmap and put it as a source for our Image. Then we just put an image instead of text like "loading"

The next step is to draw video. The same player, giving us VideoDrawing, derrived from Drawing, that can be DrawingBrush for our rectangle element.

 

if (_video == null)

            {

                _video = new VideoDrawing();

                _video.Player = _player;

            }

            _video.Rect = frameRect;

 

            if (_frame == null)

            {

                _frame = new Rectangle();

 

 

                Brush brush = new DrawingBrush(_video);

 

 

                _frame.Fill = brush;

 

 

            }

But what's going on with the performance? You can not freeze this brush, it's dynamic, but you can cache it

RenderOptions.SetCachingHint(brush, CachingHint.Cache);

                RenderOptions.SetCacheInvalidationThresholdMinimum(brush, 0.5);

                RenderOptions.SetCacheInvalidationThresholdMaximum(brush, 2.0);

Fine, we almost done. A couple of problems, in order to get something from video, we should play it first, it is not really fun, but else, you have no even first frame. So, mute it before :) There are a couple of "nice adds" in this control, so it can be used as great start for your next video player.

That's all, kids. Have a nice day.

 

Source code for this article

No comments: