Styling the scrub bar data tip on a Spark VideoPlayer control in Flex 4

The following example shows how you can customize the appearance of the data tip on the Spark VideoPlayer control’s scrub bar when scrubbing the playhead by creating a custom VideoPlayer skin and specifying a custom ScrubBar skin.

The following example(s) require Flash Player 10 and the Adobe Flex 4 SDK. To download the Adobe Flash Builder 4 trial, see www.adobe.com/products/flex/. To download the latest nightly build of the Flex 4 SDK, see opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4.

For more information on getting started with Flex 4 and Flash Builder 4, see the official Adobe Flex Team blog.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/06/17/styling-the-scrub-bar-data-tip-on-a-spark-videoplayer-control-in-flex-4/ -->
<s:Application name="VideoPlayer_ScrubBar_dataTip_test"
        xmlns:fx="http://ns.adobe.com/mxml/2009" 
        xmlns:s="library://ns.adobe.com/flex/spark" 
        xmlns:mx="library://ns.adobe.com/flex/mx">
 
    <s:VideoPlayer id="vidPl"
            source="http://helpexamples.com/flash/video/caption_video.flv"
            skinClass="skins.CustomVideoPlayerSkin"
            autoPlay="false"
            muted="true"
            horizontalCenter="0" verticalCenter="0" />
 
</s:Application>

The custom Spark VideoPlayer skin, skins/CustomVideoPlayerSkin.mxml, is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/06/17/styling-the-scrub-bar-data-tip-on-a-spark-videoplayer-control-in-flex-4/ -->
<s:SparkSkin name="CustomVideoPlayerSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
        alpha.disabledStates="0.5"
        chromeColor.fullScreenStates="0xCCCCCC">
    <!-- states -->
    <s:states>
        <s:State name="uninitialized" stateGroups="uninitializedStates, normalStates" />
        <s:State name="loading" stateGroups="loadingStates, normalStates" />
        <s:State name="ready" stateGroups="readyStates, normalStates" />
        <s:State name="playing" stateGroups="playingStates, normalStates" />
        <s:State name="paused" stateGroups="pausedStates, normalStates" />
        <s:State name="buffering" stateGroups="bufferingStates, normalStates" />
        <s:State name="playbackError" stateGroups="playbackErrorStates, normalStates" />
        <s:State name="disabled" stateGroups="disabledStates, normalStates"/>
        <s:State name="uninitializedAndFullScreen" stateGroups="uninitializedStates, fullScreenStates" />
        <s:State name="loadingAndFullScreen" stateGroups="loadingStates, fullScreenStates" />
        <s:State name="readyAndFullScreen" stateGroups="readyStates, fullScreenStates" />
        <s:State name="playingAndFullScreen" stateGroups="playingStates, fullScreenStates" />
        <s:State name="pausedAndFullScreen" stateGroups="pausedStates, fullScreenStates" />
        <s:State name="bufferingAndFullScreen" stateGroups="bufferingStates, fullScreenStates" />
        <s:State name="playbackErrorAndFullScreen" stateGroups="playbackErrorStates, fullScreenStates" />
        <s:State name="disabledAndFullScreen" stateGroups="disabledStates, fullScreenStates"/>
    </s:states>
 
    <!-- A chrome color of 0xCCCCCC in the fullScreenStates means we ignore the chromeColor property
    all together as 0xCCCCCC is essentially just a no-op color transform -->
 
    <!-- host component -->
    <fx:Metadata>
        [HostComponent("spark.components.VideoPlayer")]
    </fx:Metadata>
 
    <fx:Script fb:purpose="styling">
        <![CDATA[
            /* Define the skin elements that should not be colorized. */
            static private const exclusions:Array = ["videoDisplay", "playPauseButton", "scrubBar",
                "currentTimeDisplay", "timeDivider", "durationDisplay",
                "volumeBar", "fullScreenButton"];
 
            override protected function initializationComplete():void {
                useChromeColor = true;
                super.initializationComplete();
            }
 
            override public function get colorizeExclusions():Array {
                return exclusions;
            }
 
            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
                dropShadow.visible = getStyle("dropShadowVisible");
                super.updateDisplayList(unscaledWidth, unscaledHeight);
            }
        ]]>
 
    </fx:Script>
 
    <!-- drop shadow -->
    <s:RectangularDropShadow id="dropShadow" blurX="17" blurY="17" alpha="0.32" distance="4"
                             angle="90" color="#131313" left="0" top="0" right="0" bottom="0"
                             excludeFrom="fullScreenStates"/>
 
    <!--- Video and player controls are clipped if they exceed the size of the
    component, but the drop shadow above is not clipped and sizes to the component.
    We also set verticalScrollPosition so that when we do clip, rather than clipping
    off the bottom first, we clip off the top fist.  This is so the player controls
    are still visible when we start clipping. -->
    <s:Group id="clippedGroup" clipAndEnableScrolling="true" left="0" top="0" right="0" bottom="0"
             verticalScrollPosition="{Math.max(0, 184-clippedGroup.height)}">
 
        <!-- There's a minimum size for the video and controls.  If we go below that we are clipped. -->
        <s:Group minWidth="263" minHeight="184" left="0" right="0" top="0" bottom="0">
 
            <!-- background when the videoDisplay doesn't fill its whole spot -->
            <s:Rect bottom="1" left="1" right="1" top="1"
                    bottom.fullScreenStates="0" left.fullScreenStates="0"
                    right.fullScreenStates="0" top.fullScreenStates="0">
                <s:fill>
                    <s:SolidColor color="0x000000" />
                </s:fill>
            </s:Rect>
 
            <s:VideoDisplay id="videoDisplay" bottom="24" left="1" right="1" top="1"
                            bottom.fullScreenStates="0" left.fullScreenStates="0"
                            right.fullScreenStates="0" top.fullScreenStates="0" />
 
            <!-- video player controls -->
            <s:Group left="0" right="0" height="24" bottom="0" bottom.fullScreenStates="150">
 
                <!-- actual controls with a maxWidth in non-fullScreen mode -->
                <s:Group bottom="0" horizontalCenter="0" left="0" right="0" maxWidth.fullScreenStates="755" id="playerControls">
 
                    <s:ToggleButton id="playPauseButton" left="0" bottom="0"
                                    skinClass="spark.skins.spark.mediaClasses.normal.PlayPauseButtonSkin"
                                    skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.PlayPauseButtonSkin"
                                    focusIn="event.target.depth=1" focusOut="event.target.depth=0" />
 
                    <!-- scrubbar + the currentTime/duration labels -->
                    <s:Group left="39" right="75" top="0" bottom="0">
 
                        <!-- background for scrubbar + the currentTime/duration -->
                        <s:Rect left="0" right="0" top="0" bottom="0">
                            <s:fill>
                                <s:LinearGradient rotation="90">
                                    <s:GradientEntry color="0xFFFFFF" color.fullScreenStates="0x585858" alpha.fullScreenStates="0.55"/>
                                    <s:GradientEntry color="0xDCDCDC" color.fullScreenStates="0x1E1E1E" alpha.fullScreenStates="0.55"/>
                                </s:LinearGradient>
                            </s:fill>
                        </s:Rect>
 
                        <!-- fill highlight  (exclude in fullScreen) -->
                        <s:Rect left="1" right="1" top="1" height="11" excludeFrom="fullScreenStates">
                            <s:fill>
                                <s:SolidColor color="0xFFFFFF" alpha="0.3" />
                            </s:fill>
                        </s:Rect>
 
                        <!-- one pixel border -->
                        <s:Rect left="1" right="1" top="1" bottom="1">
                            <s:stroke>
                                <s:LinearGradientStroke weight="1" rotation="90">
                                    <s:GradientEntry color="0xFEFEFE" color.fullScreenStates="0xFFFFFF" alpha.fullScreenStates="0.12" />
                                    <s:GradientEntry color="0xEAEAEA" color.fullScreenStates="0xFFFFFF" alpha.fullScreenStates="0.09" />
                                </s:LinearGradientStroke>
                            </s:stroke>
                        </s:Rect>
 
                        <!-- border for the scrubbar/time label controls -->
                        <s:Rect left="-1" right="0" top="0" bottom="0">
                            <s:stroke>
                                <s:SolidColorStroke color="0x131313" color.fullScreenStates="0x222222" alpha.fullScreenStates="0.66"  />
                            </s:stroke>
                        </s:Rect>
 
                        <!-- scrub bar + currentTime/duration in a HorizontalLayout -->
                        <s:Group left="0" right="0" height="23" bottom="0">
                            <s:layout>
                                <s:HorizontalLayout verticalAlign="middle" gap="1" />
                            </s:layout>
 
                            <!-- spacer -->
                            <s:Rect width="7" height="1" />
 
                            <s:ScrubBar id="scrubBar" width="100%" liveDragging="true"
                                        skinClass="skins.CustomScrubBarSkin"
                                        skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.ScrubBarSkin" />
 
                            <!-- spacer -->
                            <s:Rect width="8" height="1" />
 
                            <s:Label id="currentTimeDisplay" color.fullScreenStates="0xFFFFFF" />
 
                            <s:Label id="timeDivider" text="/" color.fullScreenStates="0xFFFFFF" />
 
                            <s:Label id="durationDisplay" color.fullScreenStates="0xFFFFFF" />
 
                            <!-- spacer -->
                            <s:Rect width="8" height="1" />
                        </s:Group>
 
                    </s:Group>
 
                    <s:VolumeBar id="volumeBar" snapInterval="0.01" stepSize="0.01" liveDragging="true"
                                 right="37" bottom="0"
                                 skinClass="spark.skins.spark.mediaClasses.normal.VolumeBarSkin"
                                 skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin"
                                 focusIn="event.target.depth=1" focusOut="event.target.depth=0" />
 
                    <s:Button id="fullScreenButton" right="0" bottom="0" label="Fullscreen"
                              skinClass="spark.skins.spark.mediaClasses.normal.FullScreenButtonSkin"
                              skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.FullScreenButtonSkin"
                              focusIn="event.target.depth=1" focusOut="event.target.depth=0" />
 
                </s:Group>
 
            </s:Group>
 
            <!-- border -->
            <s:Rect left="0" right="0" top="0" bottom="0" excludeFrom="fullScreenStates">
                <s:stroke>
                    <s:SolidColorStroke color="0x131313" />
                </s:stroke>
            </s:Rect>
 
        </s:Group>
    </s:Group>
 
</s:SparkSkin>

And the custom ScrubBar skin, skins/CustomScrubBarSkin.mxml, is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/06/17/styling-the-scrub-bar-data-tip-on-a-spark-videoplayer-control-in-flex-4/ -->
<s:SparkSkin name="CustomScrubBarSkin"
             xmlns:fx="http://ns.adobe.com/mxml/2009"
             xmlns:s="library://ns.adobe.com/flex/spark"
             xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
             minHeight="14" minWidth="60"
             alpha.disabled="0.5">
    <!-- states -->
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>
 
    <fx:Metadata>
        [HostComponent("spark.components.mediaClasses.ScrubBar")]
    </fx:Metadata>
 
    <fx:Script fb:purpose="styling">
        <![CDATA[
            /* Define the skin elements that should not be colorized. */
            static private const exclusions:Array = ["track", "thumb"];
 
            override public function get colorizeExclusions():Array {
                return exclusions;
            }
 
            override protected function initializationComplete():void {
                useChromeColor = true;
                super.initializationComplete();
            }
        ]]>
    </fx:Script>
 
    <fx:Declarations>
        <!--- Defines the appearance of the ScrubBar skin's data tip. To customize the data tip's appearance, create a custom ScrubBarSkin class. -->
        <fx:Component id="dataTip">
            <s:DataRenderer minHeight="24" minWidth="40" y="-34">
                <s:RectangularDropShadow id="shadow" distance="3"
                        angle="90" color="#999999"
                        left="0" top="0" right="0" bottom="0"/>
 
                <s:Rect top="0" left="0" right="0" bottom="0">
                    <s:fill>
                        <s:SolidColor color="red" alpha="0.6"/>
                    </s:fill>
                </s:Rect>
 
                <s:Label id="labelDisplay" text="{data}"
                         horizontalCenter="0" verticalCenter="1"
                         left="5" right="5" top="5" bottom="5"
                         textAlign="center" verticalAlign="middle"
                         fontWeight="bold" color="black" fontSize="11">
                </s:Label>
            </s:DataRenderer>
        </fx:Component>
    </fx:Declarations>
 
    <!--- The skin pat that defines the video timeline. The timeline shows the current playhead location in the video, the amount of the video previously played, and the loaded in part of the video. -->
    <s:Button id="track" left="0" right="0" top="0" height="11"
              skinClass="spark.skins.spark.mediaClasses.normal.ScrubBarTrackSkin" />
 
    <s:Group id="loadedRangeArea" x="0" y="0" height="11" includeInLayout="false">
 
        <!-- inset 7 and 6 pixels because that's thumbSize/2 -->
        <s:Group left="7" right="6" top="0" bottom="0" minWidth="0">
 
            <!-- fill -->
            <s:Rect left="1" right="1" top="1" bottom="1">
                <s:fill>
                    <s:SolidColor color="0xD7D7D7" />
                </s:fill>
            </s:Rect>
 
            <!-- inner glow -->
            <!-- set height to 100%, maxHeight=1, minHeight=0 b/c only want this line to show up if there's room for it -->
            <s:Rect left="1" top="1" bottom="1" width="100%" maxWidth="1" minWidth="0">
                <s:fill>
                    <s:SolidColor color="0x000000" alpha="0.12" />
                </s:fill>
            </s:Rect>
            <s:Rect left="2" right="1" top="1" height="100%" maxHeight="1" minHeight="0">
                <s:fill>
                    <s:SolidColor color="0x000000" alpha="0.12" />
                </s:fill>
            </s:Rect>
 
            <!-- black line on right -->
            <!-- set width to 100%, maxWidth=1, minWidth=0 b/c only want this line to show up if there's room for it -->
            <s:Rect right="0" top="1" bottom="1" width="100%" maxWidth="1" minWidth="0">
                <s:fill>
                    <s:SolidColor color="0x000000" alpha="0.5"/>
                </s:fill>
            </s:Rect>
 
        </s:Group>
    </s:Group>
 
    <s:Group id="playedArea" x="0" y="0" height="11" includeInLayout="false">
 
        <!-- inset 7 and 6 pixels because that's thumbSize/2 -->
        <s:Group left="7" right="6" top="0" bottom="0" minWidth="0">
 
            <!-- inner glow -->
            <s:Rect left="1" right="1" top="1" bottom="1">
                <s:fill>
                    <s:LinearGradient rotation="90">
                        <s:GradientEntry color="0xFEFEFE"/>
                        <s:GradientEntry color="0xECECEC"/>
                    </s:LinearGradient>
                </s:fill>
            </s:Rect>
 
            <!-- fill -->
            <s:Rect left="2" right="2" top="2" bottom="2">
                <s:fill>
                    <s:LinearGradient rotation="90">
                        <s:GradientEntry color="0xFFFFFF" alpha="0.85"/>
                        <s:GradientEntry color="0xE1E1E1" alpha="0.85"/>
                    </s:LinearGradient>
                </s:fill>
            </s:Rect>
 
            <!-- black line on right -->
            <!-- set width to 100%, maxWidth=1, minWidth=0 b/c only want this line to show up if there's room for it -->
            <s:Rect right="0" top="1" bottom="1" width="100%" maxWidth="1" minWidth="0">
                <s:fill>
                    <s:SolidColor color="0x131313"/>
                </s:fill>
            </s:Rect>
 
        </s:Group>
    </s:Group>
 
    <!--- A skin part that defines a button that can be dragged along the track to increase or decrease the playhead location in the video. -->
    <s:Button id="thumb" x="0" y="0" width="14" height="19" includeInLayout="false"
              skinClass="spark.skins.spark.mediaClasses.normal.ScrubBarThumbSkin" />
 
</s:SparkSkin>

This entry is based on a beta version of the Flex 4 SDK and therefore is very likely to change as development of the Flex SDK continues. The API can (and will) change causing examples to possibly not compile in newer versions of the Flex 4 SDK.

5 thoughts on “Styling the scrub bar data tip on a Spark VideoPlayer control in Flex 4

  1. Great post!

    This works great for me if I donĀ“t use the source attribute for VideoPlayer, but when I add it the skinning stops to work. This gives me a working video without skinning or a skinned player that doesn’t play a video.

    Any ideas on why this is?

  2. error is got :

    1120: Access of undefined property CustomVideoPlayerSkin.
    1172: Definition CustomVideoPlayerSkin

  3. Why you post this examle ? For professional ? They know about this witout your help. For beginners ? I doubt that they can understand.

Comments are closed.