Setting the progress bar background gradient fill on the Spark VideoPlayer control in Flex 4

The following example shows how you can set the progress bar background color gradient fill on the Spark VideoPlayer control in Flex 4 by setting the skinClass style and modifying the playedArea object’s fill object.

Full code after the jump.

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/2009/05/17/setting-the-progress-bar-background-gradient-fill-on-the-spark-videoplayer-control-in-flex-gumbo/ -->
<s:Application name="Spark_VideoPlayer_skinClass_scrubBar_test"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/halo">

    <s:VideoPlayer id="videoPlayer"
            source="http://helpexamples.com/flash/video/cuepoints.flv"
            skinClass="skins.CustomVideoPlayerSkin"
            horizontalCenter="0"
            verticalCenter="0" />

</s:Application>

The custom VideoPlayer skin class, CustomVideoPlayerSkin.mxml, is as follows:

skins/CustomVideoPlayerSkin.mxml

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/05/17/setting-the-progress-bar-background-gradient-fill-on-the-spark-videoplayer-control-in-flex-gumbo/ -->
<s:SparkSkin name="CustomVideoPlayerSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark">
    <!-- states -->
    <s:states>
        <s:State name="connectionError" stateGroups="connectionErrorStates, normalStates" />
        <s:State name="disabled" stateGroups="disabledStates, normalStates"/>
        <s:State name="disconnected" stateGroups="disconnectedStates, normalStates"/>
        <s:State name="loading" stateGroups="loadingStates, normalStates"/>
        <s:State name="playing" stateGroups="playingStates, normalStates"/>
        <s:State name="stopped" stateGroups="stoppedStates, normalStates"/>
        <s:State name="fullScreenConnectionError" stateGroups="connectionErrorStates, fullScreenStates"/>
        <s:State name="fullScreenDisabled" stateGroups="disabledStates, fullScreenStates"/>
        <s:State name="fullScreenDisconnected" stateGroups="disconnectedStates, fullScreenStates"/>
        <s:State name="fullScreenLoading" stateGroups="loadingStates, fullScreenStates"/>
        <s:State name="fullScreenPlaying" stateGroups="playingStates, fullScreenStates"/>
        <s:State name="fullScreenStopped" stateGroups="stoppedStates, fullScreenStates"/>
    </s:states>

    <!-- host component -->
    <fx:Metadata>
        [HostComponent("spark.components.VideoPlayer")]
    </fx:Metadata>

    <fx:Script>
        /* Define the skin elements that should not be colorized. */
        static private const exclusions:Array = ["videoElement", "playPauseButton", "scrubBar",
                                                 "playheadTimeLabel", "timeDivider", "totalTimeLabel",
                                                 "volumeBar", "fullScreenButton"];

        override public function get colorizeExclusions():Array {return exclusions;}
    </fx:Script>

    <fx:Style>
        .videoTextStyle {
            font-family: Arial;
            font-size: 12px;
            color: #000000;
        }

        .videoTextStyleFullScreen {
            font-family: Arial;
            font-size: 12px;
            color: #FFFFFF;
        }
    </fx:Style>

    <!-- background when the videoElement doesn't fill its whole spot -->
    <s:Rect bottom="1" left="1" right="1" top="1">
        <s:fill>
            <s:SolidColor color="0x000000" />
        </s:fill>
    </s:Rect>

    <!--- The subcomponent that loads the video but does not define the appearance of the VideoPlayer component. -->
    <s:VideoElement id="videoElement" bottom="24" left="1" right="1" top="1" />

    <!-- video player controls -->
    <s:Group left="0" right="0" height="24" bottom="0" bottom.fullScreenStates="150">

        <!-- background for controls (in case there's extra room on the sides) only in non-fullscreen case -->
        <s:Rect left="0" right="0" top="0" bottom="0" excludeFrom="fullScreenStates">
            <s:fill>
                <s:LinearGradient rotation="90">
                    <s:GradientEntry color="0xFFFFFF"/>
                    <s:GradientEntry color="0xDCDCDC"/>
                </s:LinearGradient>
            </s:fill>
        </s:Rect>

        <!-- actual controls now with a minWidth and maxWidth -->
        <s:Group bottom="0" horizontalCenter="0" left="0" right="0" minWidth="293" maxWidth="755" id="playerControls">

            <!--- Defines the label and appearance of the Play/Pause button. -->
            <s:ToggleButton id="playPauseButton" left="0" bottom="0"
                    skinClass="spark.skins.default.VideoPlayerPlayPauseButtonSkin"
                    skinClass.fullScreenStates="spark.skins.default.VideoPlayerFullScreenPlayPauseButtonSkin" />

            <!-- scrubbar + the playHeadTime/totalTime labels -->
            <s:Group left="39" right="75" top="0" bottom="0">

                <!-- background for scrubbar + the playHeadTime/totalTime -->
                <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>

                <!-- top border for the scrubbar/time label controls -->
                <s:Line left="-1" right="0" top="0">
                    <s:stroke>
                        <s:SolidColorStroke color="0x131313" />
                    </s:stroke>
                </s:Line>

                <!-- scrub bar + playHeadTime/totalTime in a HorizontalLayout -->
                <s:Group left="0" right="0" height="24" bottom="0">
                    <s:layout>
                        <s:HorizontalLayout verticalAlign="middle" gap="1" />
                    </s:layout>

                    <!-- spacer -->
                    <s:Rect width="7" height="1">
                        <s:stroke>
                            <s:SolidColorStroke alpha="0"/>
                        </s:stroke>
                    </s:Rect>

                    <s:VideoPlayerScrubBar id="scrubBar" width="100%" liveDragging="true"
                        skinClass="skins.CustomVideoPlayerScrubBarSkin"
                        skinClass.fullScreenStates="spark.skins.default.VideoPlayerFullScreenScrubBarSkin" />

                    <!-- spacer -->
                    <s:Rect width="8" height="1">
                        <s:stroke>
                            <s:SolidColorStroke alpha="0"/>
                        </s:stroke>
                    </s:Rect>

                    <s:SimpleText id="playheadTimeLabel" styleName="videoTextStyle" styleName.fullScreenStates="videoTextStyleFullScreen" />

                    <s:SimpleText id="timeDivider" text="/" styleName="videoTextStyle" styleName.fullScreenStates="videoTextStyleFullScreen" />

                    <s:SimpleText id="totalTimeLabel" styleName="videoTextStyle" styleName.fullScreenStates="videoTextStyleFullScreen" />

                    <!-- spacer -->
                    <s:Rect width="8" height="1">
                        <s:stroke>
                            <s:SolidColorStroke alpha="0"/>
                        </s:stroke>
                    </s:Rect>
                </s:Group>

            </s:Group>

            <!--- Defines the appearance of the volume bar. -->
            <s:VideoPlayerVolumeBar id="volumeBar" valueInterval=".01" liveDragging="true" right="37" bottom="0"
                    skinClass="spark.skins.default.VideoPlayerVolumeBarSkin"
                    skinClass.fullScreenStates="spark.skins.default.VideoPlayerFullScreenVolumeBarSkin" />

            <!--- Defines the label and appearance of the Fullscreen button. -->
            <s:Button id="fullScreenButton" right="0" bottom="0" label="Fullscreen"
                    skinClass="spark.skins.default.VideoPlayerFullScreenButtonSkin"
                    skinClass.fullScreenStates="spark.skins.default.VideoPlayerFullScreenFullScreenButtonSkin" />
        </s:Group>
    </s:Group>

    <!-- border -->
    <s:Rect left="0" right="0" top="0" bottom="0">
        <s:stroke>
            <s:SolidColorStroke color="0x131313" />
        </s:stroke>
    </s:Rect>

</s:SparkSkin>

And the custom VideoPlayer scrub bar skin, CustomVideoPlayerScrubBarSkin.mxml, is as follows:

View skins/CustomVideoPlayerScrubBarSkin.mxml

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/05/17/setting-the-progress-bar-background-gradient-fill-on-the-spark-videoplayer-control-in-flex-gumbo/ -->
<s:SparkSkin name="CustomVideoPlayerScrubBarSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        minHeight="14" minWidth="60"
        alpha.disabled="0.5">
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>

    <fx:Metadata>
        [HostComponent("spark.components.VideoPlayerScrubBar")]
    </fx:Metadata>

    <fx:Script>
        /* Define the skin elements that should not be colorized. */
        static private const exclusions:Array = ["track", "thumb"];
        override public function get colorizeExclusions():Array {return exclusions;}
    </fx:Script>

    <fx:Declarations>
        <fx:Component id="dataTip">
           <s:MXMLComponent minHeight="24" minWidth="40" y="-34">
              <s:Rect top="0" left="0" right="0" bottom="0">
                    <s:fill>
                        <s:SolidColor color="0x000000" alpha=".9"/>
                    </s:fill>
                    <s:filters>
                        <s:DropShadowFilter angle="90" color="0x999999" distance="3"/>
                    </s:filters>
                </s:Rect>
                <s:SimpleText id="labelElement" text="{data}"
                         horizontalCenter="0" verticalCenter="1"
                         left="5" right="5" top="5" bottom="5"
                         textAlign="center" verticalAlign="middle"
                         fontWeight="normal" color="white" fontSize="11">
                </s:SimpleText>
            </s:MXMLComponent>
       </fx:Component>
    </fx:Declarations>

    <s:Button id="track" left="0" right="0" top="0" height="14"
              skinClass="spark.skins.default.VideoPlayerScrubBarTrackSkin" />

    <s:Group id="bufferedArea" left="0" top="0" height="14">

        <!-- inset 7 pixels because that's thumbSize/2 -->
        <s:Group left="7" right="7" 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" left="0" top="0" height="14">

        <!-- inset 7 pixels because that's thumbSize/2 -->
        <s:Group left="7" right="7" top="0" bottom="0" minWidth="0">

            <!-- fill -->
            <s:Rect left="1" right="1" top="1" bottom="1">
                <s:fill>
                    <s:LinearGradient rotation="90">
                        <s:GradientEntry color="red"/>
                        <s:GradientEntry color="purple"/>
                    </s:LinearGradient>
                </s:fill>
            </s:Rect>

            <!-- inner glow -->
            <s:Rect left="1" right="1" top="1" bottom="1">
                <s:stroke>
                    <s:LinearGradientStroke rotation="90">
                        <s:GradientEntry color="0xFEFEFE"/>
                        <s:GradientEntry color="0xECECEC"/>
                    </s:LinearGradientStroke>
                </s:stroke>
            </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>

    <s:Button id="thumb" top="0" bottom="0" width="14" includeInLayout="false"
              skinClass="spark.skins.default.VideoPlayerScrubBarThumbSkin" />

</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.

8 thoughts on “Setting the progress bar background gradient fill on the Spark VideoPlayer control in Flex 4

  1. Another really good example of the new components!

    I’ve been playing with the new spark components and I wondered; is there any plan for a spark version of the mx:ProgressBar? I’ve not found anything similar when I’ve looked.

  2. It seems that unless one is doing minor alterations, eg colors and lines, changing the layout and behaviour of individual control bar components is a major timeconsuming pain. Is this new style model supposed to be a clever replacement for rolling your own buttons and scrubber bar?

  3. Peter, I can’t get the ScrubBar’s bufferedArea bar to change. Is its value on the bar the same as the playedArea? And is it set by setting the bufferedEnd value of the ScrubBar? I think this might be a bug. Can you test this against some progressive video loading?

  4. Peter … I was right. There’s a bug in the Spark ScrubBar class. In the private method ‘calculateAreaSize’ the argument ‘value’ is clashing with the class property ‘value’ and as a result the bufferedEnd value passed in is ignored and instead the bufferedArea skin part becomes the same width as the playedArea. So you need to sub-class the ScrubBar and add these methods like so …

    override protected function updateSkinDisplayList():void
    {
        super.updateSkinDisplayList();
     
        sizeBufferedArea(newCalculateAreaSize(bufferedEnd));
        sizePlayedArea(newCalculateAreaSize(value));
    }
     
    protected function newCalculateAreaSize(v:Number):Number
    {
        var trackPos:Number = track.getLayoutBoundsX();
        var trackSize:Number = track.getLayoutBoundsWidth();
        var thumbSize:Number = thumb.getLayoutBoundsWidth();
        var range:Number = maximum - minimum;
        var thumbPos:Number = (range &gt; 0) ? (v - minimum) * ((trackSize - thumbSize) / range) : 0;
     
        return thumbSize + thumbPos;
    }

    Note that I’ve had to create my own newCalculateAreaSize method because the ScrubBar version is Private.

    :-)

  5. Could anyone tell me if there is an easy way to apply the same baseColor to the area that contains the scrubBar and the time and duration components. What I am doing is basically doing setStyle on all the components of the playerControls area but I don’t see any good way of applying the same color to that background area that is the parent of the scrubBar and the time components.

    Thanks for any help.

Comments are closed.