How do I create TimelineMarkers in code?

Jan 3, 2011 at 6:45 PM

I'd like to create visual TimelineMarkers in code. The \Samples\Samples\Markers\TextualMarkers.xaml describes it in XAML

<smf:SMFPlayer CaptionsVisibility="Visible">
    <smf:SMFPlayer.Playlist>
        <smf_media:PlaylistItem DeliveryMethod="AdaptiveStreaming" MediaSource="http://<Server>/Video.ism/manifest">
            <smf_media:PlaylistItem.TimelineMarkers>
                <smf_media:TimelineMediaMarker Begin="0:0:10" End="0:0:10" Content="Marker 1" AllowSeek="true"/>
                <smf_media:TimelineMediaMarker Begin="0:1:15" End="0:1:15" Content="Marker 2" AllowSeek="true"/>
            </smf_media:PlaylistItem.TimelineMarkers>
        </smf_media:PlaylistItem>
    </smf:SMFPlayer.Playlist>
</smf:SMFPlayer>

Where do I put this equivalent code to take effect and the visual markers will be displayed?

//Add visual markers
PlaylistItem playlistItem = Playlist[0];
for (int i = 1; i < 4; i++)
{
    int sec = i * 2;
    TimelineMediaMarker timelineMediaMarker = new TimelineMediaMarker()
    {
        Begin = TimeSpan.FromSeconds(sec),
        End = TimeSpan.FromSeconds(sec),
        Content = string.Format("{0}s Marker", sec),
        AllowSeek = true
    };

    playlistItem.TimelineMarkers.Add(timelineMediaMarker);
}

Thx, Ivan
Jan 6, 2011 at 7:22 PM

Ivan,

It looks like you have the idea. Your question is where to put the C# code you have written? In a typical MVVM project you would put that code in your view model but it really depends on your project architecture. I can tell you what I have done:

Consider a project that has Player.xaml and a PlayerViewModel.cs that implements INotifiyPropertyChanged to act as a way to bind my View Model (PlayerViewModel.cs) properties to my View (Player.xaml). In my view model I have a property for my current playlist item as well as a collection to hold all of the playlist items.

 

private PlaylistItem _currentPlaylistItem;
public PlaylistItem CurrentPlaylistItem
{
    get
    {
        return _currentPlaylistItem;
    }
    set
    {
        _currentPlaylistItem = value;
        NotifyPropertyChanged("CurrentPlaylistItem");
    }
}

 

 

public ObservableCollection<PlaylistItem> PlaylistItems { get; set; }

Then in my XAML i bind the CurrentPlaylistItem and full Playlist to my view model as well as the TimelineMarkers for to the current playlist item's markers.

 

 

<smf:SMFPlayer
    x:Name="Player"
    ...
    Playlist="{Binding PlaylistItems}"
    CurrentPlaylistItem="{Binding CurrentPlaylistItem, Mode=TwoWay}"
    TimelineMarkers="{Binding CurrentPlaylistItem.TimelineMarkers}">
</smf:SMFPlayer>

Now, how ever you get your timeline markers you can just add them to the CurrentPlaylistItem property in your view model. I load my timeline markers from a WCF service. I have code in my view model that looks something like this:
public GetVideo(int id)
{
    var client = new MyServiceClient();
    client.GetVideoByIdAsync(id);
    client.GetVideoByIdCompleted += GetVideoByIdCompleted;
}

private void GetVideoByIdCompleted(object sender, GetVideoByIdCompletedEventArgs e)
{
    var video = e.Result;
    if (video == null)
        return;

    CurrentPlaylistItem = GetPlaylistItem(video);
}

private PlaylistItem GetPlaylistItem(VideoDto video)
{
    var playlistItem = new PlaylistItem();
    playlistItem.MediaSource = video.Path;

    foreach (var bookmark in video.Bookmarks)
    {
        playlistItem.TimelineMarkers.Add(new TimelineMediaMarker
            {
                Begin = bookmark.StartTime,
                End = bookmark.EndTime,
                Content = bookmark.Name,
                AllowSeek = true
            });
    }
            
    PlaylistItems.Add(playlistItem);
    return playlistItem;
}

The end result is when I get a new video to play, I create a playlist item, add the markers, add it to the list of playlist items, and finally set that newly created playlist item to be the CurrentPlaylistItem. Since I am using data binding, the view just picks up on these changes and starts playing and you can see the timeline markers.

 

Sep 29, 2011 at 11:51 AM


Hello,

I have a player that dynamically plays different recordings, depending on the user and the choosed recording, and I want to add dynamically time line markers to each recording.

How view model can receive the video id from the web?

Thanks!

Jan 17, 2012 at 6:36 PM

Hi all,

while searching for a solution on my problem regarding my problem, I found your post here http://smf.codeplex.com/discussions/240296 which is kind of close to what I am looking for, So just want to ask for a help.

I am building a Siverlight application using MVVM to play a video using smoothstreamingmediaelement and I need to update an observablecollection on TimelineMarker Event in ViewModel. This event is attached to SmoothStreamingMediaElement.
Any instruction on how to achieve this, please help to share.

Normally, an event is fired when we click on a button or interact with an UI control. By that we can use ICommand or something like that. But in this case the event is fired without interaction to a UI control but when time is reach the markers, it is fired. I don't really know how to handle this.

Thanks


Jan 18, 2012 at 8:11 PM

truyenle,

If I understand correctly, you want to perform some action when a certain time is reached on your timeline? If that time is indicated with a Timeline Marker you can still use the ICommand approach. When I need to do this I use the TimelineMarkerReached event and bind it to an ICommand in my view model using an EventToCommand behavior. This behavior also allows me to send the event data with it so that I can have some sort of context of which marker was reached.

Jan 18, 2012 at 9:51 PM

Hi swhook52,

EventToCommand behavior, that should be the way to go. I'll give it a try and update if I success.

Thanks

Truyenle

Jan 20, 2012 at 10:56 PM

Hello,

I just want to update the status of what I have done to achieve this.

In stead of having SmoothStreamingMediaElement in xaml, I initialize an instance of it in ViewModel, then from there I can just catch the TimelineMarkerReach event and update the ObservableCollection as:

in ViewModel:

public ViewModel(IEventAggregator eventAggregator)

SmoothPlayer = new SmoothStreamingMediaElement
            {
                AutoPlay = false,
                SmoothStreamingSource = new Uri("http://localhost/DataSmoothStreaming/video.39.ism/Manifest")
            };

 // Catch the MarkerReached event
            SmoothPlayer.MarkerReached += new TimelineMarkerRoutedEventHandler(SmoothPlayer_MarkerReached);

}

void SmoothPlayer_MarkerReached(object sender, TimelineMarkerRoutedEventArgs e)

{

 // Update the Observablecollection

}

This is work for me in this case.

However, I do have other question:

The above ObservableCollection is bind to control in UI in view, if the ObservableCollection includes 3 groups of objects and each group have 20 objects, then there is no problem at all. But if the ObservableCollection hav more than 4 groups of objects and each group have 20 objects, then I do see the delay in the SmoothStreaming video. I mean the video is not smooth as it suppose to be.

Any recommendation on how to improve this would be very much appreciated.

Thanks

Truyen

Jan 22, 2012 at 3:08 PM

I recommend inheriting from SmoothStreamingMediaElement instead of creating that element in your view model. Creating the UI element in the view model loses that clear separation. With the derived class you can then override the behavior of MarkerReached.

As for the slowness I don't really know what to think. For the amount of data you are adding to the markers it should not affect performance. Is it giving you slowness in the marker reached code or on initial load? There must be some process there that is doing more than you expect.

Jan 23, 2012 at 4:13 PM

Hi swhook52,

Thank for the recommendation, I'll take that into account also.

Regarding the video jitter (slowness), it is giving the slowness in the marker reached code not the initial load. If under marker reached event handler if I only update 3 groups of objects in the UI and each group has 20 objects, then the video is ok but if it is more than that then the jitter happens and synchronize with whenever the UI is updated.

Yeah, kind of weird to me also.

Thank you for looking into this swhook52.

Truyen

Jan 23, 2012 at 5:54 PM
Edited Jan 23, 2012 at 6:45 PM

Hi swhook52,

I think I know the issue why it caused the video jittering while update UI.

The objects in the observableCollection are bind to Labels on a checkboxes. Let say each object is define as following as a model:

public class Parameter : INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (value == _name)
                    return;

                _name = value;

                OnPropertyChanged(this, "Name");
            }
        }

        private string _value;
        public string Value
        {
            get { return _value; }
            set
            {
                if (value == _value)
                    return;

                _value = value;

                OnPropertyChanged(this, "Value");
            }
        }

        private string _unit;
       
        public string Unit
        {
            get { return _unit; }
            set
            {
                if (value == _unit)
                    return;

                _unit = value;

                OnPropertyChanged(this, "Unit");
            }
        }
       
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(object sender, string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Then, these objects are group under DataGroupViewModel as:

public class DataGroupViewModel : INotifyPropertyChanged
    {
        public DataGroupViewModel(string name, ObservableCollection<Parameter> paramCollection)
        {
            _name = name;
            _parameterCollection = paramCollection;
        }

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (value == _name)
                    return;

                _name = value;

                OnPropertyChanged(this, "Name");
            }
        }

        private ObservableCollection<Parameter> _parameterCollection;
        public ObservableCollection<Parameter> ParameterCollection
        {
            get { return _parameterCollection; }
            set
            {
                if (value == _parameterCollection)
                    return;

                _parameterCollection = value;

                OnPropertyChanged(this, "ParameterCollection");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(object sender, string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Finally, I have the ObservarableCollection defined under ViewModel that will be bind to UI under view as:

private ObservableCollection<DataGroupViewModel> _dataGroupCollection;
        public ObservableCollection<DataGroupViewModel> DataGroupCollection
        {
            get { return _dataGroupCollection; }
            set
            {
                if (value == _dataGroupCollection)
                    return;

                _dataGroupCollection = value;

                OnPropertyChanged(this, "DataGroupCollection");
            }
        }

Under the view (xaml) I have these templates called ParameterTemplate and DataGroupTemplate:

<UserControl.Resources>
        <DataTemplate x:Key="ParameterTemplate">
            <Grid HorizontalAlignment="Left" VerticalAlignment="Center" Background="DarkGray" MinWidth="255">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width=".02*" />
                    <ColumnDefinition Width=".70*" />
                    <ColumnDefinition Width=".03*" />
                    <ColumnDefinition Width=".25*" />
                </Grid.ColumnDefinitions>
                <CheckBox Grid.Column="0" Background="DarkGray"/>
                <TextBlock Grid.Column="1" Text="{Binding Name}" HorizontalAlignment="Left" />
                <TextBlock Grid.Column="2" Text="{Binding Value}" HorizontalAlignment="Center" Foreground="Blue" />
                <TextBlock Grid.Column="3" Text="{Binding Unit}" HorizontalAlignment="Right" />
            </Grid>
        </DataTemplate>
        <DataTemplate x:Key="DataGroupTemplate">
            <local:GroupBox Header="{Binding Name}" HorizontalAlignment="Center" VerticalAlignment="Top" Padding="5" Margin="5" BorderBrush="Black" >
                <StackPanel Orientation="Horizontal">
                    <ItemsControl Background="DarkGray" ItemsSource="{Binding ParameterCollection}" ItemTemplate="{StaticResource ParameterTemplate}"/>
                </StackPanel>
            </local:GroupBox>
        </DataTemplate>
    </UserControl.Resources>

 <Grid x:Name="LayoutRoot" Background="Gray">
        <controls:TabControl Margin="5" Grid.Row="0" Background="DarkGray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">            

<controls:TabItem Header="ISS_EMU1" Background="Gray">
                <ItemsControl ScrollViewer.VerticalScrollBarVisibility="Disabled" Background="DarkGray" ItemsSource="{Binding DataGroupCollection}" ItemTemplate="{StaticResource DataGroupTemplate}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Horizontal" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>

        /controls:TabItem>

</controls:TabControl>
    </Grid>

By that the the parameter object include Name, Value, Unit attributes are bind to the Labels displayed next to checkboxes.

Here is the issue, Everytime the ObservableCollection update, it generates the new checkboxes although I don't bind checkboxex to anything. By that, the more parameter objects the more checkboxes are generated on updated and then cause the jitter of the video.

If I comment <CheckBox Grid.Column="0" Background="DarkGray"/> in ParameterTemplate, then the video is playing ok no jitter at all.

I need to have the checkboxes in there but don't update whenever the observablecollection update, any suggestion please help.

 

Thanks

Truyen

Jan 24, 2012 at 3:51 PM

Hi swhook52,

Just last night I figure out the reason why the Video is jitter. Here it is: 

In the MarkerReached Event handler

void SmoothPlayer_MarkerReached(object sender, TimelineMarkerRoutedEventArgs e)

{

 // Update the Observablecollection DataGroupCollection, something like

                DataGroupCollection[i].ParameterCollection = NEW_ParameterCollection;

}

The problem come from the way I update the OC, I replace the whole ParameterCollection for each element in the OC => Cause the UI regenerate the checkboxes everytime the OC is updated. And this cause the jittering in UI.

Although the checkboxes in ParameterTemplate doesn’t bind to anything but it is part of the Datatemplate, so everytime the ParameterCollection is changed, new checkboxes are created/updated => Cause the Jitter in the video.

To fixed this, I change the way to update OC as:

       DataGroupCollection.ParameterCollection[i].Name = checkBoxCollections[i].Name;

       DataGroupCollection.ParameterCollection[i].Value = checkBoxCollections[i].Value;

       DataGroupCollection.ParameterCollection[i].Unit = checkBoxCollections[i].Unit;

 

That’s solve the issue, the checkbox doesn’t update/re-generate at all. 

Well, Thank you so much for your fast response and spend time to look into this, this make me feel very good about SL community.

Truyenle

Jan 24, 2012 at 4:02 PM

That sounds about right. I figured it had to do with some processing you weren't expecting but I didn't think I could help you find that. Glad you got it working.