Megcsillan az ezüstfény - élmények 1.1

Végre eljutottam odáig, hogy a whitepaper-ök, tutorialok, get-started-ek olvasgatása után elkészítettem első Silverlight alkalmazásomat, használtam a mindenféle Expression eszközöket hozzá (erről majd írok is). Kellően bátornak érezvén magamat (és kellően nem akarván JavaScriptet bütykölni), rögtön bele az 1.1 alfába, még akkor is ha a bétája látótávolságon kívül esik. Mi jut az ember eszébe ha meghallja (vagy beírja a Googleba) a Silverlight szót: nincsen Silverlight média nélkül, valahogy mindig képek vagy videók az alkalmazás "adatai". No akkor csináljunk mi is egy ikszpluszegyedik (de első saját Open-mouthed ) videoplayer-t. Kellően fájdalommentesnek ígérkezik egy első demóalkalmazásnak, végülis a közel 100000 találat a Google-n azt mutatja, hogy nem lehetetlen ilyet csinálni. Ráadásul olvasmányainkból emlékszünk a MediaElement vezérlőre, amelyik pont videót (vagy audiót) tud lejátszani, van neki egy csomó metódusa, ráadásul kellően célratörőek: play, pause, stop, meg egynehány property, amik hasonlóan konkrétak: IsMuted, Volume, .... Szuper, a funkcionális API adott, a feladat ezek után már csak, hogy a jó metódust kell hívni jó időben és jó helyen Wink. Mi sem egyszerűbb.

Mire is lesz szükségünk, hogy mindezt a csodát véghez vigyük?

  • Microsoft Silverlight 1.1 Alpha
  • Microsoft Visual Studio Code Name "Orcas" Beta 1
  • Microsoft Silverlight Tools Alpha for Visual Studio Code Name "Orcas" Beta 1

Csak ennyi kell a fejlesztéshez, ezeket innen le is tudjuk tölteni. Mind mind béta és preview termékek, de hát mi is akarunk egy alfa techológiás fejlesztéshez? Disappointed

Beszéljünk picit a UI-ról. Ehhez dizájneri képességek, szépérzék, és rajzoló eszközök ügyes kezelésének kombójára van szükségünk. Hiszen az Expression Blend is azért is csodálatos, mert meg tudja nyitni ugyanazt a VS projektet amit én fejlesztek!, és a dizájner kolléga (mert ugye mellettetek is ül egy) máris rajzolhatja a videoplayerem kinézetét, a különböző csillivilli effekteket, itt animál, ott fadel, legyen az ő kreativitására bizva. Aki úgy érzi, hogy ezeket nem erőltetné, azt azt csinálja amit én is tettem, keres egy videoplayer kinézetet. Annyi szépérzékem van, hogy ha látok valamit, akkor meg tudom mondani, mi tetszik és mi nem, nosza keressünk ilyet. Azt is tudnám díjazni, ha rögtön XAML-ben találnám meg. Kicsit sem akarok túl sokat ugye? Nerd

Itt kell megemlítenem az Expression Media Encodert, ami ilyen szempontból nagyon hasznosnak bizonyult. Beépített featureként tud Silverlight 1.0 lejátszot csinálni egy wmv-hez. Ráadásul, van benne több előre definiált lejátszó UI, csak ki kell választanom, hogy melyiket szeretném. Ez menni fog Smile. Utánaolvasgat az ember, megtalálja, hogy mindetz az Output fülön a Job Output modul Templatek közt kell keresni:

ExpressionMediaEncoder

Mi sem egyszerűbb, görgessük le a listát. És itt érheti az első meglepetés azokat, akik nem angol nyelvű windowst használnak (igen, én sem), ugyanis a lista üres Crying. Ok, nem esünk pánikba, hisz a neten kismillió példában mindenkinek vannak templatek, akkor biztos nekem is vannak, csak meg kell keresni. És itt jön a trükk, kukkantsunk bele a C:\Program Files\Microsoft Expression\Media Encoder 1.0\Templates mappába. Milyen érdekes, hogy van benne egy "en" mappa, azon belül pedig tizenpár másik, amik neve kísértetiesen hasonlít azokra, amit a legördülő listában látnom kellene. Nézzük bele ezekbe is. Háhá, innentől nyertem, ugyanis mindegyikben van egy player.xaml fájl. Pont erre van szükségem Open-mouthed. Ha már itt vagyunk, szereljük meg ez Encodert is: "en" mappa mellé létrehozunk egy "hu" mappát (igen, magyar vista Wink ), és belemásoljuk az összes mappát az en-ből. Mostmár van nekem is az Encoderben legördülő listám telis-tele mintával, és ugye nem felejtjük el, hogy XAML-ben. A "popup" pont tetszik, jól néz ki, letisztult, vigyük Hot.

Kezdődhet a fejlesztés. Orcas indít, fájl, új projekt, Silverlight project..., adok neki nevet: VideoPlayer, természetesen C#, és a varázsló létrehozza az első Silverlight 1.1 alkalmazásom vázát:

Solution

Van itt minden, Silverlight referenciák, és néhány generált kiindulási fájl:

  • TestPage.html: a weboldal, amibe beágyazom a Silverlight tartalmat. Vagyis már a template be is ágyatta. Belinkeli a szükséges javascripteket is.
  • TestPage.html.js: ő tartalmazza a html-ben is látható createSilverlight() metódus implemetációját, ő az akivel felparaméterezem, hogy melyik XAML-t, milyne méretben, miylen tulajdonságokkal, milyen néven hova töltök be.
  • Silverlight.js: ő az, aki konkrétan be fogja tölteni a silverlight kontrollomat a megfelelő helyre. Automatikusan detektálja, hogy van-e Silverlight a kliens gépen, és ha nincs, akkor felajánlja, hogy telepít.
  • Page.xaml: a Silverlight oldalam XAML-ül megfogalmazva. Van benne egy root Canvas (erre tudok aztán majd bármit tenni), van neki névtér beállítva (xmlns nélkül nem tud élni), és mivel van hozzá C# kódom is, az x:Class="VideoPlayer.Page;assembly=ClientBin/VideoPlayer.dll" attribútumon keresztül megmondja, hogy melyik osztályban van az implementáció, és hol is van az ezt tartalmazó dll.
  • Page.xaml.cs: a C# kódom a UI-hoz, itt írom meg majd felügyelt kódban az eseménykezelőimet, mert ugye szeretnék interakciót a felahasználóval. Webes fejlesztőként könnyű volt megszoknom ezt a felállást: van egy markup, és van egy kód fájl. Van itt még valami: public void Page_Loaded(object o, EventArgs e). Ez is kísértetiesen hasonlít ahhoz, amit ASP.NET-ben megszokott az ember, egy eseménykezelő, ami az oldal betöltődésekor fut le (mármint a Silverlight oldal betöltődésekor, ami a html-ben ugye csak egy modul, ha úgy tetszik).

Ennyi előkészület után vágjunk végre bele. Nyissuk meg a Page.xaml fájlt VS-ben, és a meglévő Canvasba másoljuk bele az Expression Media Encoder popup template player.xaml root Canvasának belsejét. Mentsük el, majd Blendben nyissuk meg ugyanzet a C# projektet, és rögtön nyissuk is meg a Page.xaml fájlt. Itt vizuálisan tervezhetjük a vektorgrafikus kinézetet. Nem voltam telejesen megelégedve a felülettel, egyrészt a popup videlkedés helyett mindig látható vezérlősávot szerettem volna, kicsit más timeline-t, hangerőszabályzót, és eltelt / teljes idő kijelzését, teljes képernyőre váltást képzeltem el, másrészt volt benne pár dolog, amikkel viszont nem akartam foglalkozni (előre / hátra tekerés gomb, fejezetek kezelése, ...). Lényeg a lényeg, bele kellett nyúlnom. Nem másolom most be ide az elkészült XAML-t (nem két sor Open-mouthed), a mellékeltem a teljes projektet, abban megtalálható. Megmondom őszintén, számomra néha sokkal egyszerűbb volt VS-ben "fejleszteni" az XAML-t, mind Blend-ben rajzolva, mert sokszor volt az, hogy tudtam, hogy a valamit amit akarok azt milyen attributumnak hívják, de nem tudtam például, hogy Blendben hol kell megtalálnom. Ráadásul az Orcas-ban teljes IntelliSense van XAML szerkesztéshez a SilverLight projektben, és tökéletesen működik is. Viszont sajnos a VS-ben designer nézet nincs, miután VS-ben megírtam a markup-ot, a Blendben meg tudtam nézni, hogy mit alkottam, sőt, ott például könnyebb volt a finomhangolásokat elvégezni (width, height, top, left, ...) is

Néhány legfontosabb részletét azért megemlíteném az xaml markupnak egy konkrét példán, nézzük a Play/Pause gombot:

   <Canvas x:Name="PlayPauseButton" Width="16" Height="18" Canvas.Left="1" Canvas.Top="1">
      <Canvas.RenderTransform>
        <TransformGroup>
          <ScaleTransform ScaleX="1" ScaleY="1"/>
          <SkewTransform AngleX="0" AngleY="0"/>
          <RotateTransform Angle="0"/>
          <TranslateTransform X="0" Y="0"/>
        </TransformGroup>
      </Canvas.RenderTransform>
      <Path x:Name="Path_20" Width="16" Height="18" Canvas.Left="0" Canvas.Top="0" Stretch="Fill" StrokeThickness="0.5" StrokeLineJoin="Round" Stroke="#FF9E9E9E" Fill="#67282828" Data="F1 M 2.13976,0.25L 43.7658,0.25C 44.8095,0.25 45.6555,1.0961 45.6555,2.13977L 45.6555,20.1984C 45.6555,21.2421 44.8095,22.0882 43.7658,22.0882L 2.13976,22.0882C 1.09607,22.0882 0.25,21.2421 0.25,20.1984L 0.25,2.13977C 0.25,1.0961 1.09607,0.25 2.13976,0.25 Z "/>
      <Path x:Name="Path_21" Width="15" Height="17" Canvas.Left="0.5" Canvas.Top="0.5" Stretch="Fill" StrokeThickness="0.5" StrokeLineJoin="Round" Stroke="#677F7F7F" Data="F1 M 3.33138,1.12653L 42.5741,1.12653C 43.6178,1.12653 44.4639,1.9726 44.4639,3.0163L 44.4639,19.3219C 44.4639,20.3656 43.6178,21.2116 42.5741,21.2116L 3.33138,21.2116C 2.28769,21.2116 1.4416,20.3656 1.4416,19.3219L 1.4416,3.0163C 1.4416,1.9726 2.28769,1.12653 3.33138,1.12653 Z ">
        <Path.Fill>
          <LinearGradientBrush StartPoint="1.45695,-0.405676" EndPoint="1.45695,0.556541">
            <GradientStop Color="#00FFFFFF" Offset="0.224609"/>
            <GradientStop Color="#67FFFFFF" Offset="0.449219"/>
            <GradientStop Color="#00FFFFFF" Offset="0.979492"/>
          </LinearGradientBrush>
        </Path.Fill>
      </Path>
      <Canvas x:Name="PauseSymbol" Opacity="0.0" Width="16" Height="18" Canvas.Left="0" Canvas.Top="0">
        <Canvas x:Name="Group_22" Width="8" Height="8" Canvas.Left="4" Canvas.Top="4.5">
          <Rectangle x:Name="Rectangle_23" Width="3.5" Height="8" Canvas.Left="0" Canvas.Top="0" Stretch="Fill" StrokeThickness="0.5" StrokeLineJoin="Round" Stroke="#FF9E9E9E" Fill="#FFFFFFFF"/>
          <Rectangle x:Name="Rectangle_24" Width="3.5" Height="8" Canvas.Left="4.5" Canvas.Top="0" Stretch="Fill" StrokeThickness="0.5" StrokeLineJoin="Round" Stroke="#FF9E9E9E" Fill="#FFFFFFFF"/>
        </Canvas>
      </Canvas>
      <Canvas x:Name="PlaySymbol" Width="16" Height="18" Canvas.Left="0" Canvas.Top="0">
        <Path x:Name="Path_25" Width="8" Height="10" Canvas.Left="4" Canvas.Top="3.5" Stretch="Fill" StrokeThickness="0.5" StrokeLineJoin="Round" Stroke="#FF9E9E9E" Fill="#FFFFFFFF" Data="F1 M 18.555,15.4658L 28.2446,9.04706L 18.6298,3.00436L 18.555,15.4658 Z "/>
      </Canvas>
    </Canvas>

Egy összetett felületet általában Canvas-ban készítünk el, így annak a kontrollnak minden tagja össze van fogva egy egységbe (milyen jó lesz ez majd nekünk ha animálni vagy transzformálni akarjuk Wink), és adunk neki x:Name="PlayPauseButton" nevet is, ami azért lesz jó, mert így kódból(!) el fogjuk tudni érni, ugyanis automatikusan generálódik hozzá egy fájl, amit nem látunk (.g a kiterjesztése), de a kontrollok inicializálását elvégzni. A Canvasunkon belül összerakjuk a play és pause alakokat, és ezeknek is adunk nevet, szintén azért, hogy animálni tudjuk őket. Pozicionálni az elemetket a Canvason belül a Canvas.Left és a Canvas.Top attribútumokkal lehet, azaz hogy ez a valami az őt befoglaló canvas bal felső sarkához képest hol helyezkedik el. Fontos megjegyzeni, hogy ha a valami mérete nagyobb, mint hogy beleféjren az őt tartalmazó canvasba, akkor egész egyszerűen ami kilóg, azt levágja. Általában igaz, hogy minden érték double, koordináták, méretek, vastagságok, átlátszóság, minden. Szuper, felület megvan, csináljunk animációt hozzá. Minden gomhoz négy alap animációt fogunk elkészíteni: mouseenter, mouseleave, press, depress. Az animációkat Storyboardokban írjuk le (amiket pedig a Canvas erőforrásaiként deklarálunk), mivel a Storyboardot fogjuk tudni kódból elindítani, azaz a benne leírt animációkat lejátszani. Például az említett négy animáció a play gombhoz kapcsolódóan:

    <Storyboard x:Name="PlayPauseButton_FocusInAnimation">
      <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseButton" Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
      </DoubleAnimationUsingKeyFrames>
    </Storyboard>


    <Storyboard x:Name="PlayPauseButton_FocusOutAnimation">
      <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseButton" Storyboard.TargetProperty="(UIElement.Opacity)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.5"/>
      </DoubleAnimationUsingKeyFrames>
    </Storyboard>


    <Storyboard x:Name="PlayPauseButton_PressAnimation">
      <DoubleAnimationUsingKeyFrames  Storyboard.TargetName="PlayPauseButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="1"/>
      </DoubleAnimationUsingKeyFrames>
      <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="1"/>
      </DoubleAnimationUsingKeyFrames>
    </Storyboard>


    <Storyboard x:Name="PlayPauseButton_DepressAnimation">
      <DoubleAnimationUsingKeyFrames  Storyboard.TargetName="PlayPauseButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0"/>
      </DoubleAnimationUsingKeyFrames>
      <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0"/>
      </DoubleAnimationUsingKeyFrames>
    </Storyboard>

Láthatjuk a négy Storbyboardot, az egyszerűbbekben csak egy, a bonyolultabbakban több animáció is van. Mindenhol kulcskockás animációt használunk, azaz egy időértékhez definiáljuk az animálandó tulajdonság adott pillanatban felvevendő értékét, az átmenetet pedig magától számolja. Animációkról bővebben itt lehet olvasni. Legfontosabb tulajdonságok amit meg kell adnunk, hogy minek (Storyboard.TargetName) a mijét (Storyboard.TargetProperty) akarjuk animálni. Az elemeknél megadott x:Name tulajdonsággal hivatkozunk a felületi elemre, és megfogalmazzuk, hogy vagy közvetlen attribútumát, vagy pedig onnan kiindulva valamilyen hierarchián elérhető más, közvetett tulajdonság értékét szeretnénk animálni.

Felület megvan, animációk megvannak, már csak az eseménykezelők kellenek a gombok megnyomásához, és némi logika a videó lejátszásához. A Silverlight odlalunk Page_Loaded metódusa kiválóan alkalmas arra, hogy beregisztráljuk a megfelelő eseménykezelőket. Ha már a play gobmot néztük xaml-ben is, akkro folytassuk azzal:

        public void Page_Loaded(object o, EventArgs e)
        {
            // Required to initialize variables
            InitializeComponent();

            ...

            // PlayPause button events
            PlayPauseButton.MouseEnter += new MouseEventHandler(PlayPauseButton_MouseEnter);
            PlayPauseButton.MouseLeave += new EventHandler(PlayPauseButton_MouseLeave);
            PlayPauseButton.MouseLeftButtonDown += new MouseEventHandler(PlayPauseButton_MouseLeftButtonDown);
            PlayPauseButton.MouseLeftButtonUp += new MouseEventHandler(PlayPauseButton_MouseLeftButtonUp);

            ...
        }

Rendkívül egyszerűen történik, gondolom senkit nem lepett meg amit lát: a felületi elemektre a nevükkel hivatkozunk, és publikálnak eseményeket, amikre a megszokott módon fel is tudunk iratkozni. Az eseménykezelők ezek után pedig így néznek ki:

        void PlayPauseButton_MouseLeftButtonUp(object sender, MouseEventArgs e)
        {
            PlayPauseButton_DepressAnimation.Begin();


            if (playPauseButtonIsPlayButton)
            {
                //currently is play button, video is stopped
                //switch to Play the video
                PlaySymbol_HideAnimation.Begin();
                PauseSymbol_ShowAnimation.Begin();
                VideoPlayer.Play();
                timer.Start();
            }
            else
            {
                //currently is pause button, video is playing
                //switch to Pause the video
                PauseSymbol_HideAnimation.Begin();
                PlaySymbol_ShowAnimation.Begin();
                VideoPlayer.Pause();
            }

            playPauseButtonIsPlayButton = !playPauseButtonIsPlayButton;

        }

        void PlayPauseButton_MouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            PlayPauseButton_PressAnimation.Begin();
        }

        void PlayPauseButton_MouseLeave(object sender, EventArgs e)
        {
            PlayPauseButton_FocusOutAnimation.Begin();
        }

        void PlayPauseButton_MouseEnter(object sender, MouseEventArgs e)
        {
            PlayPauseButton_FocusInAnimation.Begin();
        }

A MouseEnter, MouseLeave, LeftButtonDown események kezelői egész egyszerűek, meghívják a lejátszandó animáció (StoryBoard) Begin metódusát. Ami picit több szót érdemel, az az kattintáshoz tartozó egérgomb felengedés esemény. Azon kívül, hogy lejátszatjuk a gomb felengedéséhez tartozó animációt, van némi logikánk is: váltani kell tudni pause és play állapotok között. Ennek megfelelően mind magának a videónak a lejátszását, mind pedig a gombon megjelenő szimbólum eltüntetését, illetve megjelenítését helyesen kell végezni (ne felejtsük el, hogy  a Play és Pause gobmot ugyanaz a felületi egység reprezentálja, csak más szimbólum látszik). Illetve, hamár itt előkerült egy timer, akkor beszéljünk róla most.

Megszoktuk, hogy egy videolejátszó mutatja, hogy éppen hol tart a videónk lejátszása. Az eltelt időt szokás szövegesen megjeleníteni, sőt, általában egy tekerősáv is szokott lenni, amin szintén szokott látszani, hogy hol járunk a videóban. Nincs ez másképp a mi esetünkben sem:

VideoPlayer

Az abrán látható bal oldali idő az aktuális pozíció ideje, a jobb oldali idő pedig a videó teljes hossza, ezek alatt pedig van a tekerősáv, ahol a piros bar mutatja az aktuális pozíciót. De hogyan is csináljuk meg? Ha megnézzük a MediaElement objektum tulajdonságait és eseményeit, akkor van olyan, hogy Position, de nincs hozzá tartozó PositionChanged esemény, tehát ezt valahogy manuálisak kell megoldani. Erre tudjuk használni a HtmlTimer olbjektumot (System.Windows.Browser névtér), amiről rengeteg helyen lehet olvasni, hogy nem elég pontos, épp ezért nem ajánaltos használni, de mivel jelen alfában nincs más (későbbi veriziókban ígérnek rendes timert), és nem is annyira időkritikus az alkalmazásunk, úgy érzem ez a pontatlanság még belefér. Ezért szintén a Page_Loaded -ban inicializálunk egy timert 500 ms-os tickegéssel, ez bővel elég ahhoz, hogy 1 másodperces pontossággal jelezzük az aktuális pozíciót. Az eseménykezelője pedig:

        private void RefreshVideoPosition()
        {
            //Set current position text
            PositionText.Text = GetTimeStr(VideoPlayer.Position);

            //set timeline progress
            PathProgressScaleTransform.ScaleX = VideoPlayer.Position.TotalMilliseconds / VideoPlayer.NaturalDuration.TimeSpan.TotalMilliseconds;
        }

A MedieElement.Position TimeSpan típusú érték mutatja a videóból már eltelt időt, ebből egy hh:mm:ss formátumot (sajnos jelent pillanatban nincs rá inteligens megoldás, és hh:mm:ss:fff formátummá konvertálja a ToString(), ráadásul hétjegyű századmásodpercet mutat). Az érdekesbb rész inkább a tekerősávon jelentkező piros csik beállítása. Transzformációt használunk hozzá, egész konkrétan nyújtást. A ScaleX értéke double, és az 1.0 jelenti a 100% széleset. Az akutális pozícióból és a videó teljes hosszából pedig elég egyszerű meghatározni százalékosan, hogy hol is járunk, és ha pont ennyi százalékra állítjuk a vízszinten skálázás értékét akkor pont kész is vagyunk (persze ennek feltétele, hogy a bar width tulajdonsága pont a 100%-os értéknek feleljen meg, mert ugye azt a szélességet fogja arányosan kicsinyíteni).

Hasonlóan működik a tekerés is. Az eseménykezelőben megnézzük, hogy hova kattintott az egérrel a felhasználó. A MouseEventArgs.GetPosition( null ) visszadja, hogy a root Canvason belül hova kattintott a felhasználó, de ha a null helyett átadunk egy felületi elemet, akkor a kattintás azon belüli pozícióját kapjuk vissza. Ez nekünk épp tökéletes, hiszen ha tudjuk, hogy az idősávol belül hová kattintott a felhasználó, akkor ennek értékéből, és az idősáv szélességéből ki tudjuk számítani, hogy százalékosan hova kell tekerni a videóban, amiből viszonylag egyszerűen meg tudjuk határozni, hogy ez TimeSpan-ben kifejezve mennyi, végül a MediaElement.Position tulajdonságot erre állítjuk. Ennyi.

        void Timeline_MouseLeftButtonUp(object sender, MouseEventArgs e)
        {
            //get the mouse click position within the timeline and set the video to that position
            SetVideoPosition(e.GetPosition(Timeline).X);
        }
        private void SetVideoPosition(double mouseXpos)
        {
            //set the position accordsing to the mouse click position
            //evaluate the timespan for the click "distance"
            double progress = mouseXpos / Timeline.Width;
            TimeSpan tsNew = new TimeSpan((long)(VideoPlayer.NaturalDuration.TimeSpan.Ticks * progress));

            //set the videoplayer position to the estimeted position
            VideoPlayer.Position = tsNew;

            //refresh the time and timeline progress
            RefreshVideoPosition();
        }

Amiről még nem beszéltünk, az a teljes képernyős nézet. Gombunk megvan hozzá, eseménykezelőit hasonlóan beregisztráltuk, ami ezek közül most fontos (azaz nem csak animál), az ez:

        void FullScreenButton_MouseLeftButtonUp(object sender, MouseEventArgs e)
        {
            FullScreenButton_DepressAnimation.Begin();
            BrowserHost.IsFullScreen = !BrowserHost.IsFullScreen;
        }

Eddig ránézésre egyszerű a történet, az IsFullScreen tulajdonságot az ellenkezőjére állítjuk. Ez tényleg tök jó, de ezzel csak azt érjük el, hogy maga a böngésző full-screen módba került. Magával a Silverlight tartalommal az égadta világon pedig semmi sem történt, mérete, pozíciója maradt a régi. Ezt szintén nekünk kell kézzel helyreigazítani, éépen ezért a Page_Loaded-ban beregisztrálunk egy eseménykezelőt, ami akkor fut le, ha teljesképernyős módba, vagy abból vissz vált a böngésző: BrowserHost.FullScreenChange += new EventHandler(BrowserHost_FullScreenChange);, és ebben az eseménykezelőben kell kézzel teljes képernyőssé tenni a videolejátszónkat:

        void BrowserHost_FullScreenChange(object sender, EventArgs e)
        {
            if (BrowserHost.IsFullScreen)
            {
                this.Width = BrowserHost.ActualWidth;
                this.Height = BrowserHost.ActualHeight;

                //resize videoplayer
                ScaleTransform sc = new ScaleTransform();
                sc.CenterX = 0;
                sc.CenterY = 0;
                sc.ScaleX = this.Width / 320; ;
                sc.ScaleY = this.Height / 260;

                this.RenderTransform = sc;
            }
            else
            {
                this.Width = 320;
                this.Height = 260;
                this.RenderTransform = null;
            }
        }

Ehhez egy transzformációt fogunk használni. Először is megnézzük, hogy most került teljes képernyőre, vagy most váltott vissza. Ha most váltott vissza onnan, az az egyszerűbb eset, szélesség, magasság fixen visszaáll az eredti méretre, a transzformációt pedig töröljük.

Ha most váltott teljesképernyőre, akkor egyrészt magát a Silverlight oldalt olyan méretűvé kell alakítani, hogy felvegye a teljes képernyőméretet. Ezt az oldal Width és Height tulajdonságainak beállításával viszonylag könnyen elvégezzük, a BrowserHost.Actual*** értékekre állítjuk. Ezután jöhet a skálázás, kiszámoljuk hogy az új szélesség és magasság értékeknek megfelelően hányszoros vízszintes és függőleges nyújtást kell alkalmazni, majd hozzárendeljük Canvas-hoz az újonan létrehozott skálázást, mint transzformációt. Mostmár az egész képernyőn a mi videólejátszónk van.

Már mindenről beszéltünk, csak arról nem, hogy hogyan kerül a videó a MediaElement-be. Persze hogy a MediaElement.Source tulajdonságon keresztül. Ha nem akarom túlbonyolítani, akkor egész egyszerűen .Source = "MyVideo.wmv" és kész is vagyok. Ez legkevésbé sem elegáns, de legalább egyszerű. És pont ezért nem így fogjuk csinálna, hanem ha már Redmond-ban szenvedtek azzal, hogy csináljanak egy Downloader obejktumot, ami képes aszinkron módon letölteni a videót, és még a letöltöttségi állapotáról is hajlandó visszajelzést adni, akkor használjuk mi is. A Paga_Loaded-ben megírjuk, hogy automatikusan töltse le a videót és állítsa is be a MediaElement forrásaként, ha letöltődött:

            Downloader downloader = new Downloader();
            downloader.DownloadProgressChanged += new EventHandler(downloader_DownloadProgressChanged);
            downloader.Completed += new EventHandler(downloader_Completed);
            downloader.DownloadFailed += new ErrorEventHandler(downloader_DownloadFailed);
            downloader.Open("GET", new Uri(HtmlPage.DocumentUri, "SilverLight.wmv"), true);
            downloader.Send();

Inicializáljuk, beállítnk neki eseménykezelőket a sikertelen letöltéshez, a letöltöttség mértékének változásához, és arra az esetre, ha telejsen késza  letöltés. Ha már van iylen esemény, akkor használjuk is ki, tájékoztassuk a felhasználót róla, hogy hány százalékban van letöltve épp a videó:

        void downloader_DownloadProgressChanged(object sender, EventArgs e)
        {
            var downloader = sender as Downloader;
            SetProgressText(String.Format("{0}%", Math.Floor(downloader.DownloadProgress * 100)));

        }

Egész egyszerűen a Downloader.DownloadProgress érétkét, ami 0.0 és 1.0 között fejeti ki százalékban, hogy hogy áll, kiírjuk a jobboldali (a későbbiekben teljes hosszként funkcionáló) szövegblokkra. A hibás letöltéssel nem különösebben foglalkozunk, helyette nézzük mi a tennivaló, ha kész a letöltés:

        private void downloader_Completed(object sender, EventArgs e)
        {
            var downloader = sender as Downloader;

            // Set the video into the Media element
            VideoPlayer.SetSource(downloader, null);

            // Set the video total length text
            SetProgressText(GetTimeStr(VideoPlayer.NaturalDuration.TimeSpan));
        }

Először is a MediaElement.SetSource() metódushívással hozzárendeljük a videófájlt a lejátszóhoz, majd pedig kiírjuk a videó teljes hosszát a jobb oldali szövegterületre.

 

Ezzel el is készült az első "hello world" alkalmazásunk Silverlight 1.1-re. Vegyes érzéseim voltak a projekt elkészítése közben. Összességében pozitív volt a élményeim vannak a fejlesztéssel kapcsolatban, a lehetőségek, az IDE támogatás, jó, pedig még nincsenek is kész a termékek Open-mouthed. Ami nekem nehézkes volt az, a designer eszközök használata a felület létrehozásakor (mint emíltettem elég sokszor inkább XAML markupot írtam), de ezt betudom annak, hogy hiányozik a designeri vénám, és bonyolult dolgokat képtelen vagyok összekattingatni rajzoló programokban. Van még pár nagyon hiányzó API, amikhez most trükközni kell, de ne felejtsük el, hogy alfa készülségi szintnél járunk. Aki kicsit is foglalkozik UI fejlesztéssel, annak feltétlenül ajánlom. Én is még csak az elején járok az ismerkedésnek az 1.1-el, és biztos nem leszek boldog a beta-ban megjelenő breaking change-ektől (bárcsak már itt lennének Hot), de minél hamarabb el kell kezdeni ízlelgetni, próbálgatni.

Csatolom az elkészült projekt forrását is, amit tessék példaként, mintsem mintaként kezelni. Fogyasszátok egészséggel!


zip VideoPlayer.zip (508 kB)




2007.07.13. 16:21:13 | Permalink | Hozzászólások: 0 | Tárgyszavak: ,


  • TFS-t mindenkinek

    Balássy György (MS RD, ASP.NET MVP, MCTS) Korábban már írtam arról, hogy a Dev10 hullámmal megváltozott az online és az offline MSDN is. A teljes képhez hozzátartozik, hogy az MSDN előfizetések is megváltoznak. Tovább »
  • OutputCache például felhasználói szerepkör alapján

    Dávid Zoltán Az egyik legerősebb ASP.NET eszköz a generált HTML tartalom gyorsítótárazásának lehetősége: Az oldal (vagy modul) tetején elhelyezett OutputCache direktívával elérhetjük, hogy az oldal (vagy modul) generált HTML tartalma több percre vagy órára is a szerver memóriájában maradjon. Ezzel brutális terheléseket tudunk kiszolgálni. A megjelenítendő tartalomnak azonban néha változnia kell. Erre szolgál az OutputCache direktíva Duration attribútuma. Egyszerűen megadjuk, hogy hány másodpercenként frissüljön a cache és kész… Tovább »


Írja meg Ön is véleményét!


Hozzászólásokat csak regisztrált, bejelentkezett felhasználóktól tudunk elfogadni!

Hozzászólások