Share via


Live Broadcast и MediaElement.MarkerReached

Многие наверное видели Silverlight Banking Demo и обратили внимание на то как приложение синхронизируется там с видео.

Для тех кто не видел, расскажу словами. По сценарию пользователь интернет-банка открывает одну из страниц приложения, на которой расположен видео-файл и некоторый набор контролов касательных самого интернет-банкинга. На видео снят вроде как финансовый советник, и он объясняет клиенту, что к чему с его счетом.

Самым эффектным моментом этой демонстрации является синхронизация, при которой дядечка в видео показывает рукой на элемент приложения и этот графический элемент на знак рукой реагирует.

Достигается такой эффект подмешиванием в видео-файл специальных маркеров, по сути текстовых строк. И они вместе с потоком идут на клиента, а клиент на Silverlight в свою очередь может на эти маркеры отреагировать, так как запрограммировали.

Собственно обработчик этот MarkerReached и о нем можно почитать по ссылке. Как создавать такие видеофайлы с маркерами рассказано в этом видео https://silverlight.net/Learn/learnvideo.aspx?video=263

Уже на этом месте можно придумать большое количество сценариев, когда плеер реагирует на точное место видео, выводит какие либо дополнительные сообщения и.т.п. В общем, все что может позволить вам ваша фантазия.

Тут то и возникает соблазн использовать этот механизм в каких нибудь живых (live) трансляциях.

Представим себе следующую ситуацию.

Тот же финансовый аналитик ведет в прямом эфире интернет-трансляции обзор рынка. По ходу своего повествования он предлагает зрителям прямо сейчас проголосовать по этому поводу. Режисер на специальном «пульте», по сути кодирующей станции нажимает на кнопку «вывести в плеере кнопки голосования». Приложение которое кодирует видео подмешивает в видео-поток строки с параметрами, тот самый маркер, и наконец, этот видео-поток с маркерами достигает специально написанный на Silverlight плеер который знает что с таким маркером делать, т.е. вывести на экран те самые кнопки голосования.

Еще раз отмечу, маркеры внутри потока, четко синхронизированы в live режиме с местом видео. Такое технологическое решение какими либо Out of band (OOB) средствами сделать достаточно трудно и это все равно будет подвержено рассинхронизации.

Надеюсь сценарий понятный. И возможно те кто читает этот пост скажут «Супер, пойду попробую». Не тут то было.

Expression Encoder которым мы можем вставить маркеры в статическое видео, не будет работать с Live потоком.

Но выход есть, который этот сценарий реализует на ура. Нужно всего лишь воспользоваться Windows Media Encoder 9.0 SDK. Можно написать приложение которое будет программно конфигурировать encoder, брать источник видео и звука, и подмешивать туда маркеры.

Ниже приведен исходный текст главной формы такого приложения, для тех кто вдруг попробует это реализовать самостоятельно.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using WMEncoderLib;

using WMPREVIEWLib;

namespace LiveBroadcastSample

{

public partial class MainForm : Form

{

WMEncoderApp mainApp = null;

IWMEncoder enc = null;

IWMEncSourceGroupCollection srcGrpCol = null;

IWMEncSourceGroup grp = null;

IWMEncSource srcAudio = null;

IWMEncVideoSource2 srcVideo = null;

IWMEncSource srcScript = null;

/*

IWMEncProfile pro=null;

IWMEncProfileCollection profColl=null;

*/

IWMEncBroadcast brdcst;

IWMEncDataViewCollection DVColl_Preview;

int lPreviewStream = -1;

WMEncDataView Preview;

IWMEncDataViewCollection DVColl_Postview;

int lPostviewStream;

WMEncDataView Postview;

public MainForm()

{

InitializeComponent();

mainApp = new WMEncoderApp();

enc = mainApp.Encoder;

WMEncoder encoder = (WMEncoder)enc;

encoder.OnAcquireCredentials += new _IWMEncoderEvents_OnAcquireCredentialsEventHandler(encoder_OnAcquireCredentials);

mainApp.Visible = false;

srcGrpCol = enc.SourceGroupCollection;

grp=srcGrpCol.Add("SG_1");

srcAudio = grp.AddSource(WMENC_SOURCE_TYPE.WMENC_AUDIO);

srcVideo = (IWMEncVideoSource2)grp.AddSource(WMENC_SOURCE_TYPE.WMENC_VIDEO);

srcScript = grp.AddSource(WMENC_SOURCE_TYPE.WMENC_SCRIPT);

srcAudio.SetInput("Microphone (2- Microsoft LifeCa", "Device", "");

srcVideo.SetInput("Microsoft LifeCam VX-3000", "Device", "");

srcScript.SetInput("", "UserScript", "");

DVColl_Postview = srcVideo.PostviewCollection;

Postview = new WMEncDataView();

lPostviewStream = DVColl_Postview.Add(Postview);

grp.set_Profile("C:\\Program Files\\Windows Media Components\\Encoder\\Profiles\\scriptstream.prx");

HttpBroadcast();

//PushDistrib(); //если передаем сигнал дальше Windows media services 2008

CreatePreview();

}

IWMEncPushDistribution PushDist=null;

//если передаем сигнал дальше на Windows media services 2008

private void PushDistrib()

{

IWMEncPushDistribution PushDist = (IWMEncPushDistribution)enc.Broadcast;

PushDist.ServerName = "192.168.12.45";

PushDist.PublishingPoint = "pubpointtest";

PushDist.Template = "pubpoint";

PushDist.AutoRemovePublishingPoint = true;

enc.PrepareToEncode(true);

}

private void CreatePreview()

{

DVColl_Preview = srcVideo.PreviewCollection;

Preview = new WMEncDataView();

IWMEncPrePreview pPrev = (IWMEncPrePreview)srcVideo.GetSourcePlugin();

pPrev.SetCaptureParent((int)panelPreview.Handle);

lPreviewStream = DVColl_Preview.Add(Preview);

}

private void HttpBroadcast()

{

brdcst = enc.Broadcast;

brdcst.set_PortNumber(WMENC_BROADCAST_PROTOCOL.WMENC_PROTOCOL_HTTP, 8080);

enc.PrepareToEncode(true);

}

//callback авторизация с Windows media services 2008 в случае трансляции на сервер

void encoder_OnAcquireCredentials(string bstrRealm,

string bstrSite, ref object pvarUser,

ref object pvarPassword, ref object plFlags)

{

pvarUser = "Administrator";

pvarPassword = "Pass@word1";

}

//старт кодирования

private void StartButtonClick(object sender, EventArgs e)

{

enc.Start();

/*

IWMEncVideoSource2 s=(IWMencVideoSource2) srcVideo.GetSourcePlugin();

bugbug

Postview.SetViewProperties(lPostviewStream, (int)panel1.Handle);

Postview.StartView(lPostviewStream);

*/

}

private void StopEncodingClick(object sender, EventArgs e)

{

enc.Stop();

}

private void SendMessageToStreamButtonClick(object sender, EventArgs e)

{

enc.SendScript(0, txtText.Text, txtMsg.Text);

}

}

}