| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package spark.components |
| { |
| |
| import flash.events.Event; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| import flash.media.Video; |
| |
| import mx.core.IUIComponent; |
| import mx.core.IVisualElement; |
| import mx.core.UIComponent; |
| import mx.core.mx_internal; |
| import mx.events.FlexEvent; |
| import mx.events.PropertyChangeEvent; |
| import mx.resources.IResourceManager; |
| import mx.resources.ResourceManager; |
| |
| |
| import org.osmf.containers.MediaContainer; |
| import org.osmf.elements.VideoElement; |
| import org.osmf.events.AudioEvent; |
| import org.osmf.events.DisplayObjectEvent; |
| import org.osmf.events.LoadEvent; |
| import org.osmf.events.MediaPlayerCapabilityChangeEvent; |
| import org.osmf.events.MediaPlayerStateChangeEvent; |
| import org.osmf.events.SeekEvent; |
| import org.osmf.events.TimeEvent; |
| import org.osmf.layout.HorizontalAlign; |
| import org.osmf.layout.LayoutMetadata; |
| import org.osmf.layout.ScaleMode; |
| import org.osmf.layout.VerticalAlign; |
| import org.osmf.media.DefaultMediaFactory; |
| import org.osmf.media.MediaElement; |
| import org.osmf.media.MediaFactory; |
| import org.osmf.media.MediaFactoryItem; |
| import org.osmf.media.MediaPlayer; |
| import org.osmf.media.MediaPlayerState; |
| import org.osmf.media.MediaResourceBase; |
| import org.osmf.media.URLResource; |
| import org.osmf.media.MediaType; |
| import org.osmf.net.NetLoader; |
| import org.osmf.net.DynamicStreamingItem; |
| import org.osmf.net.rtmpstreaming.RTMPDynamicStreamingNetLoader; |
| import org.osmf.net.DynamicStreamingResource; |
| import org.osmf.net.FMSURL; |
| import org.osmf.utils.OSMFStrings; |
| import org.osmf.utils.URL; |
| |
| import spark.components.mediaClasses.DynamicStreamingVideoItem; |
| import spark.components.mediaClasses.DynamicStreamingVideoSource; |
| import spark.primitives.BitmapImage; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched when the data is received as a download operation progresses. |
| * This event is only dispatched when playing a video by downloading it |
| * directly from a server, typically by issuing an HTTP request. |
| * It is not displatched when playing a video from a special media server, |
| * such as Flash Media Server. |
| * |
| * <p>This event may not be dispatched when the source is set to null or a playback |
| * error occurs.</p> |
| * |
| * @eventType org.osmf.events.LoadEvent.BYTES_LOADED_CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.0 |
| * @productversion Flex 4 |
| */ |
| [Event(name="bytesLoadedChange",type="org.osmf.events.LoadEvent")] |
| |
| /** |
| * Dispatched when the playhead reaches the duration for playable media. |
| * |
| * @eventType org.osmf.events.TimeEvent.COMPLETE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.0 |
| * @productversion Flex 4 |
| */ |
| [Event(name="complete", type="org.osmf.events.TimeEvent")] |
| |
| /** |
| * Dispatched when the <code>currentTime</code> property of the MediaPlayer has changed. |
| * |
| * <p>This event may not be dispatched when the source is set to null or a playback |
| * error occurs.</p> |
| * |
| * @eventType org.osmf.events.TimeEvent.CURRENT_TIME_CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.0 |
| * @productversion Flex 4 |
| */ |
| [Event(name="currentTimeChange",type="org.osmf.events.TimeEvent")] |
| |
| /** |
| * Dispatched when the <code>duration</code> property of the media has changed. |
| * |
| * <p>This event may not be dispatched when the source is set to null or a playback |
| * error occurs.</p> |
| * |
| * @eventType org.osmf.events.TimeEvent.DURATION_CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.0 |
| * @productversion Flex 4 |
| */ |
| [Event(name="durationChange", type="org.osmf.events.TimeEvent")] |
| |
| /** |
| * Dispatched when the MediaPlayer's state has changed. |
| * |
| * @eventType org.osmf.events.MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.0 |
| * @productversion Flex 4 |
| */ |
| [Event(name="mediaPlayerStateChange", type="org.osmf.events.MediaPlayerStateChangeEvent")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [DefaultProperty("source")] |
| |
| [ResourceBundle("osmf")] |
| |
| [IconFile("VideoDisplay.png")] |
| |
| /** |
| * Because this component does not define a skin for the mobile theme, Adobe |
| * recommends that you not use it in a mobile application. Alternatively, you |
| * can define your own mobile skin for the component. For more information, |
| * see <a href="http://help.adobe.com/en_US/flex/mobileapps/WS19f279b149e7481c698e85712b3011fe73-8000.html">Basics of mobile skinning</a>. |
| */ |
| [DiscouragedForProfile("mobileDevice")] |
| |
| /** |
| * The VideoDisplay class is chromeless video player that supports |
| * progressive download, multi-bitrate, and streaming video. |
| * |
| * <p><code>VideoDisplay</code> is the chromeless version that does not support skinning. |
| * It is useful when you do not want the user to interact with the control.</p> |
| * |
| * <p><code>VideoPlayer</code> is the skinnable version.</p> |
| * |
| * <p>The VideoDisplay control has the following default characteristics:</p> |
| * <table class="innertable"> |
| * <tr> |
| * <th>Characteristic</th> |
| * <th>Description</th> |
| * </tr> |
| * <tr> |
| * <td>Default size</td> |
| * <td>0 pixels wide by 0 pixels high with no content, |
| * and the width and height of the video with content</td> |
| * </tr> |
| * <tr> |
| * <td>Minimum size</td> |
| * <td>0</td> |
| * </tr> |
| * <tr> |
| * <td>Maximum size</td> |
| * <td>10000 pixels wide and 10000 pixels high</td> |
| * </tr> |
| * </table> |
| * |
| * @mxml |
| * |
| * <p>The <code><s:VideoDisplay></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:VideoDisplay |
| * <strong>Properties</strong> |
| * autoDisplayFirstFrame="true" |
| * autoPlay="true" |
| * autoRewind="true" |
| * loop="false" |
| * muted="false" |
| * pauseWhenHidden="true" |
| * scaleMode="letterbox" |
| * source="" |
| * volume="1" |
| * |
| * <strong>Events</strong> |
| * bytesLoadedChange="<i>No default</i>" |
| * complete="<i>No default</i>" |
| * currentTimeChange="<i>No default</i>" |
| * durationChange="<i>No default</i>" |
| * mediaPlayerStateChange="<i>No default</i>" |
| * |
| * /> |
| * </pre> |
| * |
| * @see spark.components.VideoPlayer |
| * |
| * @includeExample examples/VideoDisplayExample.mxml |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public class VideoDisplay extends UIComponent |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Set as the OSMF.resourceBundleFunction and used to look up |
| * strings so the OSMF RTEs are localized in Flex. |
| */ |
| private static function getResourceString(resourceName:String, |
| args:Array = null):String |
| { |
| var resourceManager:IResourceManager = ResourceManager.getInstance(); |
| return resourceManager.getString("osmf", resourceName, args); |
| } |
| |
| /** |
| * Copied from OSMF ScaleModeUtils.getScaledSize. ScaleModeUtils became |
| * an internal OSMF class in OSMF 1.0 so it is copied here. |
| * |
| * Calculates the scaled size based on the scaling algorithm. |
| * The available width and height are the width and height of the container. |
| * The intrinsic width and height are the width and height of the content. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion OSMF 1.0 |
| */ |
| private static function getScaledSize |
| ( scaleMode:String |
| , availableWidth:Number, availableHeight:Number |
| , intrinsicWidth:Number, intrinsicHeight:Number |
| ):Point |
| { |
| var result:Point; |
| |
| switch (scaleMode) |
| { |
| case ScaleMode.ZOOM: |
| case ScaleMode.LETTERBOX: |
| |
| var availableRatio:Number |
| = availableWidth |
| / availableHeight; |
| |
| var componentRatio:Number |
| = (intrinsicWidth || availableWidth) |
| / (intrinsicHeight || availableHeight); |
| |
| if ( (scaleMode == ScaleMode.ZOOM && componentRatio < availableRatio) |
| || (scaleMode == ScaleMode.LETTERBOX && componentRatio > availableRatio) |
| ) |
| { |
| result |
| = new Point |
| ( availableWidth |
| , availableWidth / componentRatio |
| ); |
| } |
| else |
| { |
| result |
| = new Point |
| ( availableHeight * componentRatio |
| , availableHeight |
| ); |
| } |
| |
| break; |
| |
| case ScaleMode.STRETCH: |
| |
| result |
| = new Point |
| ( availableWidth |
| , availableHeight |
| ); |
| break; |
| |
| case ScaleMode.NONE: |
| |
| result |
| = new Point |
| ( intrinsicWidth || availableWidth |
| , intrinsicHeight || availableHeight |
| ); |
| |
| break; |
| } |
| |
| return result; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function VideoDisplay() |
| { |
| super(); |
| |
| // create the underlying MediaPlayer class first. |
| createUnderlyingVideoPlayer(); |
| |
| // added and removed event listeners to see whether we should |
| // start or stop the video |
| addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); |
| addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler); |
| |
| // Set the OSMF hook used for localizing runtime error messages. |
| // OSMF itself has English-only messages, |
| // but higher layers like Flex can provide localized versions. |
| OSMFStrings.resourceStringFunction = getResourceString; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * This is the underlying VideoPlayer object. At some point in the |
| * future, we may change to a new implementation. |
| */ |
| mx_internal var videoPlayer:MediaPlayer; |
| |
| /** |
| * @private |
| * This is the underlying container used to display |
| * the underlying videoPlayer. |
| */ |
| mx_internal var videoContainer:MediaContainer; |
| |
| /** |
| * @private |
| * How the correct media elements are created based on the url of |
| * the resource. |
| */ |
| mx_internal var mediaFactory:MediaFactory; |
| |
| /** |
| * @private |
| * Whether the video is on the display list or not |
| */ |
| private var _isOnDisplayList:Boolean = false; |
| |
| /** |
| * @private |
| * Whether the we should play the video when the video |
| * becomes playable again (visible, on display list, and enabled). |
| * This starts out as true, but when we pause the video is changePlayback(), |
| * we set it to false. Also, when a user action occurs, like pause() or play() |
| * or stop() is called, we set it to false as well. |
| */ |
| private var playTheVideoOnVisible:Boolean = true; |
| |
| /** |
| * @private |
| */ |
| private var effectiveVisibility:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var effectiveVisibilityChanged:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var effectiveEnabled:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var effectiveEnabledChanged:Boolean = false; |
| |
| /** |
| * @private |
| * We do different things in the source setter based on if we |
| * are initialized or not. |
| */ |
| private var initializedOnce:Boolean = false; |
| |
| /** |
| * @private |
| * Keeps track of the muted property while loading up a |
| * video because of autoDisplayFirstFrame. |
| */ |
| private var beforeLoadMuted:Boolean; |
| |
| /** |
| * @private |
| * Keeps track whether we are loading up the |
| * video because of autoDisplayFirstFrame. |
| * |
| * <p>In this case we are in "state1" of loading, |
| * which means we are waiting for the READY |
| * MediaPlayerStateChangeEvent and haven't done anything yet.</p> |
| */ |
| private var inLoadingState1:Boolean; |
| |
| /** |
| * @private |
| * Keeps track whether we are loading up the |
| * video because of autoDisplayFirstFrame. |
| * |
| * <p>In this case we are in "state2" of loading, |
| * which means have set videoPlayer.displayObject.visible=false |
| * and videoPlayer.muted=true. We've also called play() and are |
| * waiting for the DimensionChangeEvent.</p> |
| * |
| * <p>Note: At this point, inLoadingState1 = true as well.</p> |
| */ |
| private var inLoadingState2:Boolean; |
| |
| /** |
| * @private |
| * Keeps track whether we are loading up the |
| * video because of autoDisplayFirstFrame. |
| * |
| * <p>In this case we are in "state3" of loading, |
| * which means have received the DimensionChangeEvent and have called |
| * pause() and seek(0). We are currently waiting for the |
| * SEEK_END event, at which point we will be completely loaded up.</p> |
| * |
| * <p>Note: At this point, inLoadingState1 = inLoadingState2 = true.</p> |
| */ |
| private var inLoadingState3:Boolean; |
| |
| /** |
| * @private |
| * |
| * Old value of videoPlayer.currentTimeUpdateInterval. We store the old |
| * value when we are taken off the stage and pauseWhenHidden is true |
| * (removedFromStageHandler). We restore the old value when we are added |
| * to the stage (addedToStageHandler). The value is used in setupSource() |
| * to turn the timers back on if they were disabled. The value if also used |
| * to check if we should turn off the timers in videoPlayer_seekChangeHandler |
| * and videoPlayer_currentTimeChangeHandler. |
| * This is done to keep this component from being pinned in memory by the timer |
| * associated the currentTimeUpdateInterval property. |
| */ |
| private var oldCurrentTimeUpdateInterval:Number = NaN; |
| |
| /** |
| * @private |
| * |
| * Old value of videoPlayer.bytesLoadedUpdateInterval. We store the old |
| * value when we are taken off the stage and pauseWhenHidden is true |
| * (removedFromStageHandler). We restore the old value when we are added |
| * to the stage (addedToStageHandler). The value is used in setupSource() |
| * to turn the timers back on if they were disabled. The value if also used |
| * to check if we should turn off the timers in videoPlayer_seekChangeHandler |
| * and videoPlayer_currentTimeChangeHandler. |
| * This is done to keep this component from being pinned in memory by the timer |
| * associated the bytesLoadedUpdateInterval property. |
| */ |
| private var oldBytesLoadedUpdateInterval:Number = NaN; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // autoDisplayFirstFrame |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _autoDisplayFirstFrame:Boolean = true; |
| |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * If <code>autoPlay = false</code>, then |
| * <code>autoDisplayFirstFrame</code> controls whether the video |
| * is loaded when the <code>source</code> is set. |
| * If <code>autoDisplayFirstFrame</code> |
| * is set to <code>true</code>, then the first frame of the video is |
| * loaded and the video is sized correctly. |
| * If <code>autoDisplayFirstFrame</code> is set to <code>false</code>, then no |
| * connection to the source is made, the first frame is not shown, |
| * and the video's size is not determined until someone tries to play |
| * the video. |
| * |
| * <p>If <code>autoPlay = true</code>, then this flag is ignored.</p> |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get autoDisplayFirstFrame():Boolean |
| { |
| return _autoDisplayFirstFrame; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set autoDisplayFirstFrame(value:Boolean):void |
| { |
| _autoDisplayFirstFrame = value; |
| } |
| |
| //---------------------------------- |
| // autoPlay |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _autoPlay:Boolean = true; |
| |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * Specifies whether the video starts playing immediately when the |
| * <code>source</code> property is set. |
| * If <code>true</code>, the video file immediately begins to buffer and |
| * play. |
| * |
| * <p>Even if <code>autoPlay</code> is set to <code>false</code>, Flex |
| * starts loading the video after the <code>initialize()</code> method is |
| * called. |
| * For Flash Media Server, this means creating the stream and loading |
| * the first frame to display. |
| * In the case of an HTTP download, Flex starts downloading the stream |
| * and shows the first frame.</p> |
| * |
| * <p>If <code>playWhenHidden</code> is set to <code>false</code>, then |
| * <code>autoPlay</code> also affects what happens when the video |
| * comes back on stage and is enabled and visible.</p> |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get autoPlay():Boolean |
| { |
| return _autoPlay; |
| } |
| |
| /** |
| * @private (setter) |
| */ |
| public function set autoPlay(value:Boolean):void |
| { |
| if (autoPlay == value) |
| return; |
| |
| _autoPlay = value; |
| |
| // call changePlayback() but don't immediately play or pause |
| // based on this change to autoPlay |
| changePlayback(false, false); |
| } |
| |
| //---------------------------------- |
| // autoRewind |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * Specifies whether the FLV file should rewind to the first frame |
| * when play stops, either by calling the <code>stop()</code> method or by |
| * reaching the end of the stream. |
| * |
| * <p>This property has no effect for live streaming video.</p> |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get autoRewind():Boolean |
| { |
| return videoPlayer.autoRewind; |
| } |
| |
| public function set autoRewind(value:Boolean):void |
| { |
| videoPlayer.autoRewind = value; |
| } |
| |
| //---------------------------------- |
| // bytesLoaded |
| //---------------------------------- |
| |
| [Inspectable(Category="General", defaultValue="0")] |
| [Bindable("bytesLoadedChange")] |
| [Bindable("mediaPlayerStateChange")] |
| |
| /** |
| * The number of bytes of data that have been downloaded into the application. |
| * |
| * @return The number of bytes of data that have been downloaded into the application. |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get bytesLoaded():Number |
| { |
| return videoPlayer.bytesLoaded; |
| } |
| |
| //---------------------------------- |
| // bytesTotal |
| //---------------------------------- |
| |
| [Inspectable(Category="General", defaultValue="0")] |
| [Bindable("mediaPlayerStateChange")] |
| |
| /** |
| * The total size in bytes of the data being downloaded into the application. |
| * |
| * @return The total size in bytes of the data being downloaded into the application. |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get bytesTotal():Number |
| { |
| return videoPlayer.bytesTotal; |
| } |
| |
| //---------------------------------- |
| // currentTime |
| //---------------------------------- |
| |
| [Inspectable(Category="General", defaultValue="0")] |
| [Bindable("currentTimeChange")] |
| [Bindable("mediaPlayerStateChange")] |
| |
| /** |
| * Current time of the playhead, measured in seconds, |
| * since the video starting playing. |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get currentTime():Number |
| { |
| return videoPlayer.currentTime; |
| } |
| |
| //---------------------------------- |
| // duration |
| //---------------------------------- |
| |
| [Inspectable(Category="General", defaultValue="0")] |
| [Bindable("durationChange")] |
| [Bindable("mediaPlayerStateChange")] |
| |
| /** |
| * Duration of the video's playback, in seconds |
| * |
| * @return The total running time of the video in seconds |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get duration():Number |
| { |
| return videoPlayer.duration; |
| } |
| |
| //---------------------------------- |
| // loop |
| //---------------------------------- |
| |
| [Inspectable(Category="General", defaultValue="false")] |
| |
| /** |
| * Indicates whether the media should play again after playback has completed. |
| * The <code>loop</code> property takes precedence over the <code>autoRewind</code> |
| * property, so if loop is set to <code>true</code>, the <code>autoRewind</code> |
| * property is ignored. |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get loop():Boolean |
| { |
| return videoPlayer.loop; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set loop(value:Boolean):void |
| { |
| if (loop == value) |
| return; |
| |
| videoPlayer.loop = value; |
| } |
| |
| //---------------------------------- |
| // mediaPlayerState |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="uninitialized")] |
| [Bindable("mediaPlayerStateChange")] |
| |
| /** |
| * The current state of the video. See |
| * org.osmf.media.MediaPlayerState for available values. |
| * |
| * @default uninitialized |
| * |
| * @see org.osmf.media.MediaPlayerState |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get mediaPlayerState():String |
| { |
| return videoPlayer.state; |
| } |
| |
| //---------------------------------- |
| // muted |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="false")] |
| [Bindable("volumeChanged")] |
| |
| /** |
| * Set to <code>true</code> to mute the video, <code>false</code> |
| * to unmute the video. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get muted():Boolean |
| { |
| // if inLoadingState2, we've got to |
| // fake the muted value |
| if (inLoadingState2) |
| return beforeLoadMuted; |
| |
| return videoPlayer.muted; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set muted(value:Boolean):void |
| { |
| if (muted == value) |
| return; |
| |
| // if inLoadingState2, don't change muted...just fake it |
| if (inLoadingState2) |
| { |
| beforeLoadMuted = value; |
| return; |
| } |
| |
| videoPlayer.muted = value; |
| } |
| |
| //---------------------------------- |
| // pauseWhenHidden |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage variable for pauseWhenHidden |
| */ |
| private var _pauseWhenHidden:Boolean = true; |
| |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * Controls whether the video continues to play when it is |
| * "hidden". The video is "hidden" when either <code>visible</code> |
| * is set to <code>false</code> on it or one of its ancestors, |
| * or when the video is taken off |
| * of the display list. If set to <code>true</code>, the video |
| * pauses playback until the video is visible again. If set to |
| * <code>false</code> the video continues to play when it is hidden. |
| * |
| * <p>If the video is disabled (or one of the video's parents are |
| * disabled), the video pauses as well, but when it is re-enabled, |
| * the video does not play again. This behavior is not controlled through |
| * <code>pauseWhenHidden</code>; it is defined in the VideoDisplay component.</p> |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get pauseWhenHidden():Boolean |
| { |
| return _pauseWhenHidden; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set pauseWhenHidden(value:Boolean):void |
| { |
| if (_pauseWhenHidden == value) |
| return; |
| |
| _pauseWhenHidden = value; |
| |
| if (_pauseWhenHidden) |
| { |
| addVisibilityListeners(); |
| computeEffectiveVisibilityAndEnabled(); |
| } |
| else |
| { |
| removeVisibilityListeners(); |
| } |
| |
| // call changePlayback(). If we're invisible or off the stage, |
| // setting this to true can pause the video. However, setting it |
| // to false should have no immediate impact. |
| changePlayback(value, false); |
| } |
| |
| //---------------------------------- |
| // playing |
| //---------------------------------- |
| |
| [Inspectable(category="General")] |
| [Bindable("mediaPlayerStateChange")] |
| |
| /** |
| * Contains <code>true</code> if the video is playing or is attempting to play. |
| * |
| * <p>The video may not be currently playing, as it may be seeking |
| * or buffering, but the video is attempting to play.</p> |
| * |
| * @see #play() |
| * @see #pause() |
| * @see #stop() |
| * @see #autoPlay |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get playing():Boolean |
| { |
| return videoPlayer.playing; |
| } |
| |
| //---------------------------------- |
| // scaleMode |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _scaleMode:String = ScaleMode.LETTERBOX; |
| |
| [Inspectable(Category="General", enumeration="none,stretch,letterbox,zoom", defaultValue="letterbox")] |
| |
| /** |
| * The <code>scaleMode</code> property describes different ways of |
| * sizing the video content. |
| * You can set <code>scaleMode</code> to |
| * <code>"none"</code>, <code>"stretch"</code>, |
| * <code>"letterbox"</code>, or <code>"zoom"</code>. |
| * |
| * <p>If no width, height, or constraints are specified on the component, |
| * this property has no effect.</p> |
| * |
| * @default "letterbox" |
| * |
| * @see org.osmf.display.ScaleMode |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get scaleMode():String |
| { |
| return _scaleMode; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set scaleMode(value:String):void |
| { |
| if (scaleMode == value) |
| return; |
| |
| switch(value) |
| { |
| case ScaleMode.NONE: |
| _scaleMode = ScaleMode.NONE; |
| break; |
| case ScaleMode.STRETCH: |
| _scaleMode = ScaleMode.STRETCH; |
| break; |
| case ScaleMode.LETTERBOX: |
| _scaleMode = ScaleMode.LETTERBOX; |
| break; |
| case ScaleMode.ZOOM: |
| _scaleMode = ScaleMode.ZOOM; |
| break; |
| default: |
| _scaleMode = ScaleMode.LETTERBOX; |
| break; |
| } |
| |
| // set scaleMode on the videoElement object |
| if (videoPlayer.media) |
| { |
| var layout:LayoutMetadata = videoPlayer.media. |
| getMetadata(LayoutMetadata.LAYOUT_NAMESPACE) as LayoutMetadata; |
| if (layout) |
| layout.scaleMode = _scaleMode; |
| } |
| |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // source |
| //---------------------------------- |
| |
| private var _source:Object; |
| private var sourceChanged:Boolean; |
| |
| [Inspectable(category="General", defaultValue="null")] |
| [Bindable("sourceChanged")] |
| |
| /** |
| * The video source. |
| * |
| * <p>For progressive download, the source is just a path or URL pointing |
| * to the video file to play.</p> |
| * |
| * <p>For streaming (recorded streaming, live streaming, |
| * or multi-bitrate streaming), the source property is a |
| * DynamicStreamingVideoSource object. If you just want to play |
| * a recorded or live streaming video with no multi-bitrate support, |
| * you can just pass in a String URL pointing to the video |
| * stream. However, if you do this, the streamType is assumed to be "any," |
| * and you don't have as much control over the streaming as you would if |
| * you used the DynamicStreamingVideoSource object.</p> |
| * |
| * <p>Note: Setting the source on a MediaPlayerStateChangeEvent.LOADING or a |
| * MediaPlayerStateChangeEvent.READY is not recommended if the source was |
| * previously set. This could cause an infinite loop or an RTE. |
| * If you must do an operation like that, wait an additional frame to |
| * set the source.</p> |
| * |
| * @see spark.components.mediaClasses.DynamicStreamingVideoSource |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get source():Object |
| { |
| return _source; |
| } |
| |
| /** |
| * @private (setter) |
| */ |
| public function set source(value:Object):void |
| { |
| _source = value; |
| |
| // if we haven't initialized, let's wait to set up the |
| // source in commitProperties() as it is dependent on other |
| // properties, like autoPlay and enabled, and those may not |
| // be set yet, especially if they are set via MXML. |
| // Otherwise, if we have initialized, let's just set up the |
| // source immediately. This way people can change the source |
| // and immediately call methods like seek(). |
| if (!initializedOnce) |
| { |
| sourceChanged = true; |
| invalidateProperties(); |
| } |
| else |
| { |
| setUpSource(); |
| } |
| |
| dispatchEvent(new Event("sourceChanged")); |
| } |
| |
| //---------------------------------- |
| // thumbnailSource |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _thumbnailSource:Object; |
| |
| /** |
| * @private |
| * Group that holds the BitmapImage for the thumbnail |
| */ |
| private var thumbnailGroup:Group; |
| |
| /** |
| * @private |
| * BitmapImage for the thumbnail |
| */ |
| private var thumbnailBitmapImage:BitmapImage; |
| |
| [Inspectable(Category="General")] |
| |
| /** |
| * @private |
| * Thumbnail source is an internal property used to replace the video with a thumbnail. |
| * This is for places where we just want to load in a placeholder object for the video |
| * and don't want to incur the extra load-time or memory of loading up the video. |
| * |
| * <p>Thumbnail source can take any valid source that can be passed in to |
| * <code>spark.primitivies.BitmapImage#source</code>.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| mx_internal function get thumbnailSource():Object |
| { |
| return _thumbnailSource; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set thumbnailSource(value:Object):void |
| { |
| if (_thumbnailSource == value) |
| return; |
| |
| _thumbnailSource = value; |
| |
| // if we haven't initialized, let's wait to set up the |
| // source in commitProperties() as it is dependent on other |
| // properties, like autoPlay and enabled, and those may not |
| // be set yet, especially if they are set via MXML. |
| // Otherwise, if we have initialized, let's just set up the |
| // source immediately. This way people can change the source |
| // and immediately call methods like seek(). |
| if (!initializedOnce) |
| { |
| sourceChanged = true; |
| invalidateProperties(); |
| } |
| else |
| { |
| setUpThumbnailSource(); |
| } |
| } |
| |
| /** |
| * @private |
| * Sets up the thumbnail source for use. |
| */ |
| private function setUpThumbnailSource():void |
| { |
| if (thumbnailSource) |
| { |
| // create thumbnail group if there isn't one |
| if (!thumbnailGroup) |
| { |
| thumbnailBitmapImage = new BitmapImage(); |
| thumbnailBitmapImage.includeInLayout = false; |
| |
| thumbnailGroup = new Group(); |
| // add thumbnailGroup to the display list first in case |
| // bitmap needs to check its layoutDirection. |
| addChild(thumbnailGroup); |
| thumbnailGroup.clipAndEnableScrolling = true; |
| thumbnailGroup.addElement(thumbnailBitmapImage); |
| } |
| |
| // if thumbnailGroup isn't on the display list, then add it. |
| if (!this.contains(thumbnailGroup)) |
| addChild(thumbnailGroup); |
| |
| thumbnailBitmapImage.source = thumbnailSource; |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| else |
| { |
| if (thumbnailGroup) |
| { |
| // null out the source and remove the thumbnail group |
| if (thumbnailBitmapImage) |
| thumbnailBitmapImage.source = null; |
| if (this.contains(thumbnailGroup)) |
| removeChild(thumbnailGroup); |
| invalidateSize(); |
| } |
| } |
| } |
| |
| //---------------------------------- |
| // videoObject |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="null")] |
| |
| /** |
| * The underlying flash player <code>flash.media.Video</code> object. |
| * |
| * <p>If the source is <code>null</code>, then there may be no |
| * underlying <code>flash.media.Video object</code> yet. In that |
| * case, <code>videoObject</code> returns <code>null</code>.</p> |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get videoObject():Video |
| { |
| return videoPlayer.displayObject as Video; |
| } |
| |
| //---------------------------------- |
| // volume |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="1.0", minValue="0.0", maxValue="1.0")] |
| [Bindable("volumeChanged")] |
| |
| /** |
| * The volume level, specified as a value between 0 and 1. |
| * |
| * @default 1 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get volume():Number |
| { |
| return videoPlayer.volume; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set volume(value:Number):void |
| { |
| if (volume == value) |
| return; |
| |
| videoPlayer.volume = value; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function commitProperties():void |
| { |
| super.commitProperties(); |
| |
| initializedOnce = true; |
| |
| if (effectiveVisibilityChanged || effectiveEnabledChanged) |
| { |
| // if either visibility of enabled changed, re-compute them here |
| computeEffectiveVisibilityAndEnabled(); |
| |
| // if visibility changed and we care about it, we can |
| // cause a play or a pause depending on our visibility |
| var causePause:Boolean = false; |
| var causePlay:Boolean = false; |
| if (effectiveVisibilityChanged && pauseWhenHidden) |
| { |
| causePause = !effectiveVisibility; |
| causePlay = effectiveVisibility; |
| } |
| |
| // if enabled changed, we can only cause a pause. |
| // Re-enabling a component doesn't cause a play. |
| if (effectiveEnabledChanged) |
| { |
| if (!effectiveEnabled) |
| causePause = true; |
| } |
| |
| changePlayback(causePause, causePlay); |
| |
| effectiveVisibilityChanged = false; |
| effectiveEnabledChanged = false; |
| } |
| |
| if (sourceChanged) |
| { |
| sourceChanged = false; |
| |
| if (thumbnailSource) |
| setUpThumbnailSource(); |
| else |
| setUpSource(); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function measure() : void |
| { |
| super.measure(); |
| |
| var intrinsicWidth:Number; |
| var intrinsicHeight:Number; |
| |
| // if showing the thumbnail, just use the thumbnail's size |
| if (thumbnailSource && thumbnailGroup) |
| { |
| intrinsicWidth = thumbnailBitmapImage.getPreferredBoundsWidth(); |
| intrinsicHeight = thumbnailBitmapImage.getPreferredBoundsHeight(); |
| } |
| else |
| { |
| // If there is no media the width/height will be NaN. |
| // Convert it to zero for our purposes. |
| intrinsicWidth = videoPlayer.mediaWidth; |
| if (isNaN(intrinsicWidth)) |
| intrinsicWidth = 0; |
| |
| intrinsicHeight = videoPlayer.mediaHeight; |
| if (isNaN(intrinsicHeight)) |
| intrinsicHeight = 0; |
| } |
| |
| measuredWidth = intrinsicWidth; |
| measuredHeight = intrinsicHeight; |
| |
| // Determine whether 'width' and 'height' have been set. |
| var bExplicitWidth:Boolean = !isNaN(explicitWidth); |
| var bExplicitHeight:Boolean = !isNaN(explicitHeight); |
| |
| // If only one has been set, calculate the other based on aspect ratio. |
| if (bExplicitWidth && !bExplicitHeight && intrinsicWidth > 0) |
| measuredHeight = explicitWidth * intrinsicHeight / intrinsicWidth; |
| else if (bExplicitHeight && !bExplicitWidth && intrinsicHeight > 0) |
| measuredWidth = explicitHeight * intrinsicWidth / intrinsicHeight; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void |
| { |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| |
| // if just showing the thumbnail, push this width/height in to the thumbnail |
| // otherwise we'll push it in to the video object |
| if (thumbnailSource && thumbnailGroup) |
| { |
| // get what the size of our image should be |
| var newSize:Point = getScaledSize(scaleMode, unscaledWidth, unscaledHeight, |
| thumbnailBitmapImage.getPreferredBoundsWidth(), thumbnailBitmapImage.getPreferredBoundsHeight()); |
| |
| // set the thumbnailGroup to be the size of the component. |
| // set the bitmap image to be the size it should be according to OSMF |
| thumbnailGroup.setLayoutBoundsSize(unscaledWidth, unscaledHeight); |
| thumbnailBitmapImage.setLayoutBoundsSize(newSize.x, newSize.y); |
| |
| // center the thumnail image within the thumbnail group. |
| // if it's too big to fit, the thumbnail group will crop it |
| thumbnailBitmapImage.x = (unscaledWidth - newSize.x)/2; |
| thumbnailBitmapImage.y = (unscaledHeight - newSize.y)/2; |
| |
| return; |
| } |
| |
| videoContainer.width = Math.floor(unscaledWidth); |
| videoContainer.height = Math.floor(unscaledHeight); |
| |
| // need to validate the gateway immediately--otherwise we may run out of synch |
| // as they may wait a frame by default before validating (see SDK-24880) |
| videoContainer.validateNow(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Pauses playback without moving the playhead. |
| * If playback is already is paused or is stopped, this method has no |
| * effect. |
| * |
| * <p>To start playback again, call the <code>play()</code> method.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function pause():void |
| { |
| // check to see if we can call methods on the video player object yet |
| if (!videoPlayerResponsive()) |
| return; |
| |
| playTheVideoOnVisible = false; |
| |
| // if we're loading up, then we will pause automatically, so let's |
| // not interrupt this process |
| // if inLoadingState1 && pausable, then let loading state handle it |
| // if inLoadingState1 && !pausable, then let the loading state handle it |
| // if !inLoadingState1 && pausable, then just pause |
| // if !inLoadingState1 && !pausable, then load (if needed to show first frame) |
| if (!inLoadingState1 && videoPlayer.canPause) |
| videoPlayer.pause(); |
| else if (!videoPlayer.canPause && autoDisplayFirstFrame) |
| load(); |
| } |
| |
| /** |
| * Causes the video to play. Can be called while the video is |
| * paused, stopped, or while the video is already playing. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function play():void |
| { |
| // check to see if we can call methods on the video player object yet |
| if (!videoPlayerResponsive()) |
| return; |
| |
| playTheVideoOnVisible = false; |
| |
| // if we're loading up, use a special method to cancel the load |
| // and to start playing again. Otherwise, go ahead and play |
| if (inLoadingState1) |
| cancelLoadAndPlay(); |
| else if (videoPlayer.canPlay) |
| videoPlayer.play(); |
| } |
| |
| /** |
| * Seeks to given time in the video. If the video is playing, |
| * continue playing from that point. If the video is paused, seek to |
| * that point and remain paused. If the video is stopped, seek to |
| * that point and enters paused state. |
| * This method has no effect with live video streams. |
| * |
| * <p>If time is less than 0 or NaN, throws exception. If time |
| * is past the end of the stream, or past the amount of file |
| * downloaded so far, then attempts to seek and, if it fails, it then recovers.</p> |
| * |
| * <p>The <code>currentTime</code> property might not have the expected value |
| * immediately after you call <code>seek()</code>. |
| * For a progressive download, |
| * you can seek only to a keyframe; therefore, a seek takes you to the |
| * time of the first keyframe after the specified time.</p> |
| * |
| * <p><strong>Note</strong>: When streaming, a seek always goes to the precise specified |
| * time even if the source FLV file doesn't have a keyframe there.</p> |
| * |
| * <p>Seeking is asynchronous, so if you call the <code>seek()</code> method, |
| * <code>currentTime</code> does not update immediately. </p> |
| * |
| * @param time The seek time, in seconds. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function seek(time:Number):void |
| { |
| // check to see if we can call methods on the video player object yet |
| if (!videoPlayerResponsive()) |
| return; |
| |
| // TODO (rfrishbe): could handle what to do if this gets called when loading() better. |
| // Need to store where we want to seek to. |
| if (videoPlayer.canSeek) |
| videoPlayer.seek(time); |
| } |
| |
| /** |
| * Stops video playback. If <code>autoRewind</code> is set to |
| * <code>true</code>, rewinds to first frame. If video is already |
| * stopped, has no effect. To start playback again, call |
| * <code>play()</code>. |
| * |
| * @see #autoRewind |
| * @see #play() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function stop():void |
| { |
| // check to see if we can call methods on the video player object yet |
| if (!videoPlayerResponsive()) |
| return; |
| |
| playTheVideoOnVisible = false; |
| |
| // if we're loading up, then we will stop automatically, so let's |
| // not interrupt this process |
| // if inLoadingState1 && pausable, then let loading state handle it |
| // if inLoadingState1 && !pausable, then let the loading state handle it |
| // if !inLoadingState1 && pausable, then just pause |
| // if !inLoadingState1 && !pausable, then load (if needed to show first frame) |
| if (!inLoadingState1 && videoPlayer.canPause) |
| videoPlayer.stop(); |
| else if (!videoPlayer.canPause && autoDisplayFirstFrame) |
| load(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Private Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * If the video player is responsive, then methods can be called on the underlying |
| * video player. |
| */ |
| private function videoPlayerResponsive():Boolean |
| { |
| // can't call any methods before we've initialized |
| if (!initializedOnce) |
| return false; |
| |
| // if displaying a thumbnail, no methods can be called b/c there's no video |
| // loaded up |
| if (thumbnailSource) |
| return false; |
| |
| // if the video player's in a bad state, we can't do anything |
| if (videoPlayer.state == MediaPlayerState.PLAYBACK_ERROR || |
| videoPlayer.state == MediaPlayerState.UNINITIALIZED || |
| videoPlayer.state == MediaPlayerState.LOADING) |
| { |
| return false; |
| } |
| |
| // if no source, return false as well |
| if (!source) |
| return false; |
| |
| // otherwise, we are in a good state and have a source, so let's go |
| return true; |
| } |
| |
| /** |
| * @private |
| */ |
| private function createUnderlyingVideoPlayer():void |
| { |
| // create new video player |
| videoPlayer = new MediaPlayer(); |
| videoContainer = new MediaContainer(); |
| videoContainer.clipChildren = true; |
| |
| mediaFactory = new DefaultMediaFactory(); |
| |
| // remove unsupport media types |
| var unsupportedMediaTypes:Array = ["org.osmf.elements.video.dvr.dvrcast", |
| "org.osmf.elements.image", |
| "org.osmf.elements.swf"]; |
| |
| for each (var mediaType:String in unsupportedMediaTypes) |
| { |
| var mediaFactoryItem:MediaFactoryItem = mediaFactory.getItemById(mediaType); |
| if (mediaFactoryItem) |
| mediaFactory.removeItem(mediaFactoryItem); |
| } |
| |
| // internal events |
| videoPlayer.addEventListener(DisplayObjectEvent.MEDIA_SIZE_CHANGE, videoPlayer_mediaSizeChangeHandler); |
| videoPlayer.addEventListener(AudioEvent.VOLUME_CHANGE, videoPlayer_volumeChangeHandler); |
| videoPlayer.addEventListener(AudioEvent.MUTED_CHANGE, videoPlayer_mutedChangeHandler); |
| |
| // public events |
| videoPlayer.addEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE, videoPlayer_mediaPlayerStateChangeHandler); |
| videoPlayer.addEventListener(TimeEvent.CURRENT_TIME_CHANGE, dispatchEvent); |
| videoPlayer.addEventListener(LoadEvent.BYTES_LOADED_CHANGE, dispatchEvent); |
| videoPlayer.addEventListener(TimeEvent.DURATION_CHANGE, videoPlayer_durationChangeHandler); |
| videoPlayer.addEventListener(TimeEvent.COMPLETE, dispatchEvent); |
| |
| addChild(videoContainer); |
| } |
| |
| /** |
| * @private |
| * Sets up the source for use. |
| */ |
| private function setUpSource():void |
| { |
| // clean up any listeners from the old source, especially if we |
| // are in the processing of loading that video file up |
| cleanUpSource() |
| |
| // if was playing a previous video, let's remove it now |
| if (videoPlayer.media && videoContainer.containsMediaElement(videoPlayer.media)) |
| { |
| videoContainer.removeMediaElement(videoPlayer.media); |
| } |
| |
| var videoElement:org.osmf.media.MediaElement = null; |
| |
| // check for 4 cases: streaming video, progressive download, |
| // an IMediaResource, or a VideoElement. |
| // The latter 2 are undocumented but allowed for flexibility until we |
| // can support OSMF better after they ship OSMF 1.0. At that point, support |
| // for a source as an IMediaResource or a VideoElement may be removed. |
| if (source is DynamicStreamingVideoSource) |
| { |
| // the streaming video case. |
| // build up a DynamicStreamingResource to pass in to OSMF |
| var streamingSource:DynamicStreamingVideoSource = source as DynamicStreamingVideoSource; |
| var dsr:DynamicStreamingResource; |
| |
| // check for two cases for host: String and URL. |
| // Technically, we only support URL, but we secretly allow |
| // them to send in an OSMF URL or FMSURL here to help resolve any ambiguity |
| // around serverName vs. streamName. |
| if (streamingSource.host is String) |
| { |
| dsr = new DynamicStreamingResource(streamingSource.host as String, |
| streamingSource.streamType); |
| } |
| else if (streamingSource.host is URL) |
| { |
| dsr = new DynamicStreamingResource(URL(streamingSource.host).host, |
| streamingSource.streamType); |
| } |
| |
| if (dsr) |
| { |
| var n:int = streamingSource.streamItems.length; |
| var item:DynamicStreamingVideoItem; |
| var dsi:DynamicStreamingItem; |
| var streamItems:Vector.<DynamicStreamingItem> = new Vector.<DynamicStreamingItem>(n); |
| |
| for (var i:int = 0; i < n; i++) |
| { |
| item = streamingSource.streamItems[i]; |
| dsi = new DynamicStreamingItem(item.streamName, item.bitrate); |
| streamItems[i] = dsi; |
| } |
| dsr.streamItems = streamItems; |
| |
| dsr.initialIndex = streamingSource.initialIndex; |
| |
| // add video type metadata so if the URL is ambiguous, OSMF will |
| // know what type of file we're trying to connect to |
| dsr.mediaType = MediaType.VIDEO; |
| |
| videoElement = new org.osmf.elements.VideoElement(dsr, new RTMPDynamicStreamingNetLoader()); |
| } |
| } |
| else if (source is String && source != "") |
| { |
| var urlResource:URLResource = new URLResource(source as String); |
| videoElement = mediaFactory.createMediaElement(urlResource); |
| |
| // If the url could not be resolved to a media element then try |
| // telling osmf the media is a video and try again. |
| // We do not specify the media type as video the first time, |
| // so we can have the chance to play audio. |
| if (videoElement == null) |
| { |
| urlResource.mediaType = MediaType.VIDEO; |
| videoElement = mediaFactory.createMediaElement(urlResource); |
| } |
| } |
| else if (source is MediaResourceBase) |
| { |
| videoElement = mediaFactory.createMediaElement(MediaResourceBase(source)); |
| } |
| else if (source is org.osmf.elements.VideoElement) |
| { |
| videoElement = source as org.osmf.elements.VideoElement; |
| } |
| |
| // reset the visibilityPausedTheVideo flag |
| playTheVideoOnVisible = true; |
| // set up videoPlayer.autoPlay based on whether this.autoPlay is |
| // set and whether we are visible and the other typical conditions. |
| changePlayback(false, false); |
| |
| // if we're not going to autoPlay (or couldn't autoPlay because |
| // we're hidden or for some other reason), but we need to seek |
| // to the first frame, then we have to do this on our own |
| // by using our load() method. |
| if (videoElement && (!autoPlay || !shouldBePlaying) && autoDisplayFirstFrame) |
| load(); |
| |
| // set videoPlayer's element to the newly constructed VideoElement |
| // set the newly constructed videoElement's gateway to be the videoGateway |
| videoPlayer.media = videoElement; |
| |
| if (videoElement) |
| { |
| // If we are loading a video, make sure the timers are restored in case |
| // they had been disabled. The timers will be disabled again if we are |
| // only loading the first frame. |
| if (!isNaN(oldCurrentTimeUpdateInterval)) |
| { |
| videoPlayer.currentTimeUpdateInterval = oldCurrentTimeUpdateInterval; |
| videoPlayer.bytesLoadedUpdateInterval = oldBytesLoadedUpdateInterval; |
| } |
| |
| if (videoElement.getMetadata(LayoutMetadata.LAYOUT_NAMESPACE) == null) |
| { |
| var layout:LayoutMetadata = new LayoutMetadata(); |
| layout.scaleMode = scaleMode; |
| layout.verticalAlign = VerticalAlign.MIDDLE; |
| layout.horizontalAlign = HorizontalAlign.CENTER; |
| layout.percentWidth = 100; |
| layout.percentHeight = 100; |
| videoElement.addMetadata(LayoutMetadata.LAYOUT_NAMESPACE, layout); |
| } |
| |
| if (videoElement && !videoContainer.containsMediaElement(videoElement) ) |
| { |
| videoContainer.addMediaElement(videoElement); |
| } |
| } |
| else |
| { |
| // if our source is null, let's invalidateSize() here. |
| // if it's a bad source, we'll get a playbackError and invalidate |
| // the size down there. If it's a good source, we'll get a |
| // dimensionChange event and invalidate the size in there. |
| invalidateSize(); |
| } |
| } |
| |
| /** |
| * @private |
| * Our own internal load() method to handle the case |
| * where autoPlay = false and autoDisplayFirstFrame = true |
| * so that we can load up the video, figure out its size, |
| * and show the first frame |
| */ |
| private function load():void |
| { |
| inLoadingState1 = true; |
| |
| // wait until we can mute, play(), pause(), and seek() before doing anything. |
| // We should be able to do all of these operations on the READY state change event. |
| videoPlayer.addEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE, videoPlayer_mediaPlayerStateChangeHandlerForLoading); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // pauseWhenHidden: Event handlers and Private Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Whether the video should be playing based on enabled, |
| * pauseWhenHidden, whether it's on the display list, and its |
| * effective visibility. |
| */ |
| private function get shouldBePlaying():Boolean |
| { |
| // if disabled, return false |
| if (!effectiveEnabled) |
| return false; |
| |
| // if we want to look at visibility, check to |
| // see if we are on the display list and check out |
| // effectiveVisibility (which looks up our parent chain |
| // to make sure us and all of our ancestors are visible) |
| if (pauseWhenHidden) |
| { |
| if (!_isOnDisplayList) |
| return false; |
| |
| if (!effectiveVisibility) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @private |
| * This method will pause or play the video by looking at the state of |
| * the component and determining whether it should play or pause. This |
| * method gets called when an important event occurs, such as |
| * the component being added/removed from the stage, the component's |
| * effective visibility changing, or when autoPlay is set. |
| * |
| * <p>Only certain events are "action events" which can cause the video |
| * to pause or play immediately. For example, when autoPlay is set to |
| * true/false, that shouldn't cause any immediate action, but changePlayback() |
| * is still called so that autoPlay can be set on the underlying media player.</p> |
| * |
| * <p>Actions that can pause the video are: |
| * <ul> |
| * <li>Changes in effective enablement</li> |
| * <li>Changes in effective visibility</li> |
| * <li>Changes in staging (added or removed from display list)</li> |
| * <li>Setting pauseWhenHidden = true</li> |
| * </ul></p> |
| * |
| * <p>Actions that can play the video are: |
| * <ul> |
| * <li>Changes in effective visibility</li> |
| * <li>Changes in staging (added or removed from display list)</li> |
| * </ul></p> |
| * |
| * @param causePause Whether this action can cause a currently playing video to pause |
| * @param causePlay Whether this action can cause a currently paused video to play |
| */ |
| private function changePlayback(causePause:Boolean, causePlay:Boolean):void |
| { |
| // if we shouldn't be playing, we pause the video. |
| // if we come back up and should be playing, we will |
| // start playing the video again if the video wasn't paused |
| // by the user or developer and autoPlay is true. |
| if (shouldBePlaying) |
| { |
| videoPlayer.autoPlay = autoPlay; |
| |
| // only play the video if visibility caused it to pause |
| // (instead of a user or developer calling video.pause()). |
| // Also, only play if autoPlay is true. Otherwise when |
| // the visibility changes, we won't automatically |
| // play the video |
| if (causePlay && (playTheVideoOnVisible && autoPlay)) |
| { |
| playTheVideoOnVisible = false; |
| |
| // set autoplay and call play() if the |
| // source has loaded up and it's playable |
| if (inLoadingState1) |
| cancelLoadAndPlay(); |
| else if (videoPlayer.canPlay) |
| videoPlayer.play(); |
| } |
| } |
| else |
| { |
| // there are really three states the video player can |
| // be in with respect to play vs. paused: |
| // 1) playing |
| // 2) paused |
| // 3) loading |
| // Here we are checking if we are playing or loading |
| // and going to play soon (autoPlay = true) |
| if (causePause && (playing || (videoPlayer.state == MediaPlayerState.LOADING && autoPlay))) |
| playTheVideoOnVisible = true; |
| |
| // always set autoPlay to false here and |
| // if pausable, pause the video |
| videoPlayer.autoPlay = false; |
| if (causePause) |
| { |
| // if we're loading up, then we will pause automatically, so let's |
| // not interrupt this process |
| // if inLoadingState1 && pausable, then let loading state handle it |
| // if inLoadingState1 && !pausable, then let the loading state handle it |
| // if !inLoadingState1 && pausable, then just pause |
| // if !inLoadingState1 && !pausable, then load (if needed to show first frame) |
| if (!inLoadingState1 && videoPlayer.canPause) |
| videoPlayer.pause(); |
| else if (!videoPlayer.canPause && autoDisplayFirstFrame) |
| load(); |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Cancels the load, no matter what state it's in, and starts to play(). |
| */ |
| private function cancelLoadAndPlay():void |
| { |
| if (inLoadingState1) |
| { |
| if (!inLoadingState2) |
| { |
| // first step |
| |
| // Don't need to do anything but set inLoadingState1 = false (done down below). |
| // This is handled in videoPlayer_mediaPlayerStateChangeHandlerForLoading which will still |
| // be fired and will handle calling videoPlayer.play() without the rest of the loading |
| // junk because inLoadingState1 = false now |
| } |
| else if (!inLoadingState3) |
| { |
| // second step |
| videoPlayer.muted = beforeLoadMuted; |
| |
| if (videoPlayer.displayObject) |
| videoPlayer.displayObject.visible = true; |
| |
| videoPlayer.removeEventListener(TimeEvent.CURRENT_TIME_CHANGE, videoPlayer_currentTimeChangeHandler); |
| videoPlayer.removeEventListener(MediaPlayerCapabilityChangeEvent.CAN_SEEK_CHANGE, videoPlayer_canSeekChangeHandler); |
| } |
| else |
| { |
| // third step |
| videoPlayer.removeEventListener(SeekEvent.SEEKING_CHANGE, videoPlayer_seekChangeHandler); |
| videoPlayer.muted = beforeLoadMuted; |
| if (videoPlayer.displayObject) |
| videoPlayer.displayObject.visible = true; |
| |
| // wasn't playing |
| if (videoPlayer.canPlay) |
| videoPlayer.play(); |
| } |
| |
| inLoadingState1 = false; |
| inLoadingState2 = false; |
| inLoadingState3 = false; |
| } |
| } |
| |
| /** |
| * @private |
| * Cancels the load, no matter what state it's in. This is used when changing the source. |
| */ |
| private function cleanUpSource():void |
| { |
| // TODO (rfrishbe): very similar to cancelLoadAndPlay(). Should collapse it down. |
| |
| // always remove listener as we could be out of loadState1 but still "loading to play" |
| videoPlayer.removeEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE, videoPlayer_mediaPlayerStateChangeHandlerForLoading); |
| |
| if (inLoadingState1) |
| { |
| if (!inLoadingState2) |
| { |
| // first step |
| |
| // Just need to remove event listeners as we did above |
| } |
| else if (!inLoadingState3) |
| { |
| // second step |
| videoPlayer.muted = beforeLoadMuted; |
| if (videoPlayer.displayObject) |
| videoPlayer.displayObject.visible = true; |
| |
| // going to call pause() now to stop immediately |
| videoPlayer.pause(); |
| } |
| else |
| { |
| // third step |
| videoPlayer.removeEventListener(SeekEvent.SEEKING_CHANGE, videoPlayer_seekChangeHandler); |
| videoPlayer.muted = beforeLoadMuted; |
| if (videoPlayer.displayObject) |
| videoPlayer.displayObject.visible = true; |
| |
| // already called pause(), so don't do anything |
| } |
| |
| inLoadingState1 = false; |
| inLoadingState2 = false; |
| inLoadingState3 = false; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function addedToStageHandler(event:Event):void |
| { |
| _isOnDisplayList = true; |
| |
| // add listeners to current parents to see if their visibility has changed |
| if (pauseWhenHidden) |
| addVisibilityListeners(); |
| |
| addEnabledListeners(); |
| |
| computeEffectiveVisibilityAndEnabled(); |
| |
| // When added to the stage, restore some videoPlayer timers that we had |
| // disabled when we went offstage. |
| if (!isNaN(oldCurrentTimeUpdateInterval)) |
| { |
| videoPlayer.currentTimeUpdateInterval = oldCurrentTimeUpdateInterval; |
| videoPlayer.bytesLoadedUpdateInterval = oldBytesLoadedUpdateInterval; |
| |
| oldCurrentTimeUpdateInterval = NaN; |
| oldBytesLoadedUpdateInterval = NaN; |
| } |
| |
| // being added to the display list will not pause the video, but |
| // it may play the video if pauseWhenHidden = true |
| changePlayback(false, pauseWhenHidden); |
| } |
| |
| /** |
| * @private |
| */ |
| private function removedFromStageHandler(event:Event):void |
| { |
| _isOnDisplayList = false; |
| |
| // remove listeners from old parents |
| if (pauseWhenHidden) |
| removeVisibilityListeners(); |
| |
| removeEnabledListeners(); |
| |
| // Stop the timers associated with these intervals when we go |
| // offscreen so we are not pinned in memory. Save the old |
| // values of the timers so we can restore them when we come |
| // back on stage. |
| if (pauseWhenHidden) |
| { |
| oldCurrentTimeUpdateInterval = videoPlayer.currentTimeUpdateInterval; |
| oldBytesLoadedUpdateInterval = videoPlayer.bytesLoadedUpdateInterval; |
| videoPlayer.currentTimeUpdateInterval = -1; |
| videoPlayer.bytesLoadedUpdateInterval = -1; |
| } |
| |
| // being removed from the display list will pause the video if |
| // pauseWhenHidden = true |
| changePlayback(pauseWhenHidden, false); |
| } |
| |
| /** |
| * @private |
| * Add event listeners for SHOW and HIDE on all the ancestors up the parent chain. |
| * Adding weak event listeners just to be safe. |
| */ |
| private function addVisibilityListeners():void |
| { |
| var current:IVisualElement = this; |
| while (current) |
| { |
| // add visibility listeners to the parent |
| current.addEventListener(FlexEvent.HIDE, visibilityChangedHandler, false, 0, true); |
| current.addEventListener(FlexEvent.SHOW, visibilityChangedHandler, false, 0, true); |
| |
| // add listeners to the design layer too |
| if (current.designLayer) |
| { |
| current.designLayer.addEventListener("layerPropertyChange", |
| designLayer_layerPropertyChangeHandler, false, 0, true); |
| } |
| |
| current = current.parent as IVisualElement; |
| } |
| } |
| |
| /** |
| * @private |
| * Add event listeners for "enabledChanged" event on all ancestors up the parent chain. |
| * Adding weak event listeners just to be safe. |
| */ |
| private function addEnabledListeners():void |
| { |
| var current:IVisualElement = this; |
| while (current) |
| { |
| current.addEventListener("enabledChanged", enabledChangedHandler, false, 0, true); |
| current.addEventListener("enabledChanged", enabledChangedHandler, false, 0, true); |
| |
| current = current.parent as IVisualElement; |
| } |
| } |
| |
| /** |
| * @private |
| * Remove event listeners for SHOW and HIDE on all the ancestors up the parent chain. |
| */ |
| private function removeVisibilityListeners():void |
| { |
| var current:IVisualElement = this; |
| while (current) |
| { |
| current.removeEventListener(FlexEvent.HIDE, visibilityChangedHandler, false); |
| current.removeEventListener(FlexEvent.SHOW, visibilityChangedHandler, false); |
| |
| if (current.designLayer) |
| { |
| current.designLayer.removeEventListener("layerPropertyChange", |
| designLayer_layerPropertyChangeHandler, false); |
| } |
| |
| current = current.parent as IVisualElement; |
| } |
| } |
| |
| /** |
| * @private |
| * Remove event listeners for "enabledChanged" event on all ancestors up the parent chain. |
| */ |
| private function removeEnabledListeners():void |
| { |
| var current:IVisualElement = this; |
| while (current) |
| { |
| current.removeEventListener("enabledChanged", enabledChangedHandler, false); |
| current.removeEventListener("enabledChanged", enabledChangedHandler, false); |
| |
| current = current.parent as IVisualElement; |
| } |
| } |
| |
| /** |
| * @private |
| * Event call back whenever the visibility of us or one of our ancestors |
| * changes |
| */ |
| private function visibilityChangedHandler(event:FlexEvent):void |
| { |
| effectiveVisibilityChanged = true; |
| invalidateProperties(); |
| } |
| |
| /** |
| * @private |
| * Event call back whenever the visibility of our designLayer or one of our parent's |
| * designLayers change. |
| */ |
| private function designLayer_layerPropertyChangeHandler(event:PropertyChangeEvent):void |
| { |
| if (event.property == "effectiveVisibility") |
| { |
| effectiveVisibilityChanged = true; |
| invalidateProperties(); |
| } |
| } |
| |
| /** |
| * @private |
| * Event call back whenever the enablement of us or one of our ancestors |
| * changes |
| */ |
| private function enabledChangedHandler(event:Event):void |
| { |
| effectiveEnabledChanged = true; |
| invalidateProperties(); |
| } |
| |
| /** |
| * @private |
| */ |
| private function computeEffectiveVisibilityAndEnabled():void |
| { |
| // start out with true visibility and enablement |
| // then loop up parent-chain to see if any of them are false |
| effectiveVisibility = true; |
| effectiveEnabled = true; |
| var current:IVisualElement = this; |
| |
| while (current) |
| { |
| if (!current.visible || |
| (current.designLayer && !current.designLayer.effectiveVisibility)) |
| { |
| effectiveVisibility = false; |
| if (!effectiveEnabled) |
| break; |
| } |
| |
| if (current is IUIComponent && !IUIComponent(current).enabled) |
| { |
| effectiveEnabled = false; |
| if (!effectiveVisibility) |
| break; |
| } |
| |
| current = current.parent as IVisualElement; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function videoPlayer_volumeChangeHandler(event:AudioEvent):void |
| { |
| dispatchEvent(new Event("volumeChanged")); |
| } |
| |
| /** |
| * @private |
| */ |
| private function videoPlayer_mutedChangeHandler(event:AudioEvent):void |
| { |
| dispatchEvent(new Event("volumeChanged")); |
| } |
| |
| /** |
| * @private |
| * Event handler for mediaPlayerStateChange event. |
| */ |
| private function videoPlayer_mediaPlayerStateChangeHandler(event:MediaPlayerStateChangeEvent):void |
| { |
| // if the event change caused us to go in to a state where |
| // nothing is loaded up and we've no chance of getting a |
| // dimensionChangeEvent, then let's invalidate our size here |
| if (event.state == MediaPlayerState.PLAYBACK_ERROR) |
| invalidateSize(); |
| |
| // this is a public event, so let's re-dispatch it |
| dispatchEvent(event); |
| } |
| |
| /** |
| * @private |
| * Event handler for mediaPlayerStateChange event--used only |
| * when trying to load up the video without playing it. |
| */ |
| private function videoPlayer_mediaPlayerStateChangeHandlerForLoading(event:MediaPlayerStateChangeEvent):void |
| { |
| // only come in here when we want to load the video without playing it. |
| //trace("videoPlayer_mediaPlayerStateChangeHandlerForLoading: mediaPlayerState = " + event.state); |
| |
| // wait until we are ready so that we can set mute, play, pause, and seek |
| if (event.state == MediaPlayerState.READY) |
| { |
| // now that we are loading up, let's remove the event listener: |
| videoPlayer.removeEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE, videoPlayer_mediaPlayerStateChangeHandlerForLoading); |
| |
| // if we are already playing() for some reason because someone called play(), then |
| // we don't need to do anything. |
| if (videoPlayer.playing) |
| return; |
| |
| // if this load wasn't cancelled, then we'll do the load stuff. |
| // otherwise, we'll just cause play(). |
| if (inLoadingState1) |
| { |
| //trace("videoPlayer_mediaPlayerStateChangeHandlerForLoading: inLoadingState1"); |
| |
| beforeLoadMuted = videoPlayer.muted; |
| videoPlayer.muted = true; |
| |
| if (videoPlayer.displayObject) |
| videoPlayer.displayObject.visible = false; |
| |
| inLoadingState2 = true; |
| } |
| |
| // call play(), here, then wait to call pause() and seek(0) in the |
| // mediaSizeChangeHandler |
| //trace("videoPlayer_mediaPlayerStateChangeHandlerForLoading: call videoPlayer.play()"); |
| videoPlayer.play(); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function videoPlayer_mediaSizeChangeHandler(event:DisplayObjectEvent):void |
| { |
| //trace("videoPlayer_mediaSizeChangeHandler"); |
| invalidateSize(); |
| |
| // if we're loading up the video, then let's finish the load in here |
| if (inLoadingState2) |
| { |
| //trace("videoPlayer_mediaSizeChangeHandler: inLoadingState2"); |
| |
| if (videoPlayer.canSeek && videoPlayer.canSeekTo(0)) |
| { |
| //trace("videoPlayer_mediaSizeChangeHandler: canSeek to first frame"); |
| inLoadingState3 = true; |
| |
| // Don't call pause and seek inside this handler because OSMF is |
| // not expecting us to change its HTTPStreamingState value in |
| // HTTPNetStream.onMainTimer as a result of dispatching this |
| // event (see SDK-27028). |
| callLater(pauseAndSeekCallBack); |
| } |
| else if (duration < 0) |
| { |
| // Work around for negative durations - FM-1009 |
| // We want to seek to the first frame but we can't because the |
| // duration of the video is reported as negative. As a work around, |
| // listen for the first time change event and then pause the video. |
| //trace("videoPlayer_mediaSizeChangeHandler: negative duration - wait for first current time change event"); |
| videoPlayer.addEventListener(TimeEvent.CURRENT_TIME_CHANGE, videoPlayer_currentTimeChangeHandler); |
| } |
| else |
| { |
| //trace("videoPlayer_mediaSizeChangeHandler: waiting for media to become seekable"); |
| |
| // wait for the media to become seekable. |
| videoPlayer.addEventListener(MediaPlayerCapabilityChangeEvent.CAN_SEEK_CHANGE, videoPlayer_canSeekChangeHandler); |
| } |
| } |
| } |
| |
| private function pauseAndSeekCallBack():void |
| { |
| // the seek(0) is asynchronous so let's add an event listener to see when it's finsished: |
| videoPlayer.addEventListener(SeekEvent.SEEKING_CHANGE, videoPlayer_seekChangeHandler); |
| |
| // called play(), now call pause() and seek(0); |
| videoPlayer.pause(); |
| videoPlayer.seek(0); |
| |
| } |
| |
| /** |
| * @private |
| * Wait until the media is seekable before we call pause() and seek(). |
| */ |
| private function videoPlayer_canSeekChangeHandler(event:Event):void |
| { |
| //trace("videoPlayer_canSeekChangeHandler: seeking = " + videoPlayer.canSeek); |
| |
| videoPlayer.removeEventListener(MediaPlayerCapabilityChangeEvent.CAN_SEEK_CHANGE, videoPlayer_canSeekChangeHandler); |
| |
| if (inLoadingState2) |
| { |
| if (videoPlayer.canSeek && videoPlayer.canSeekTo(0)) |
| { |
| inLoadingState3 = true; |
| |
| // Don't call pause and seek inside this handler because OSMF is |
| // not expecting us to change its HTTPStreamingState value in |
| // HTTPNetStream.onMainTimer as a result of dispatching this |
| // event (see SDK-27028). |
| callLater(pauseAndSeekCallBack); |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Event handler for seekEnd events. We only use this |
| * when trying to load up the video without playing it. |
| * This will be called after the video has loaded up and |
| * we have finished seeking back to the first frame. |
| */ |
| private function videoPlayer_seekChangeHandler(event:SeekEvent):void |
| { |
| if (!event.seeking) |
| { |
| inLoadingState1 = false; |
| inLoadingState2 = false; |
| inLoadingState3 = false; |
| |
| videoPlayer.removeEventListener(SeekEvent.SEEKING_CHANGE, videoPlayer_seekChangeHandler); |
| videoPlayer.muted = beforeLoadMuted; |
| if (videoPlayer.displayObject) |
| videoPlayer.displayObject.visible = true; |
| |
| // Disable the TimeEvents again that we had |
| // enabled for loading a video while offstage. |
| if (!isNaN(oldCurrentTimeUpdateInterval)) |
| { |
| videoPlayer.currentTimeUpdateInterval = -1; |
| videoPlayer.bytesLoadedUpdateInterval = -1; |
| } |
| } |
| } |
| |
| |
| /** |
| * @private |
| * |
| * Work around for negative durations - see FM-1009. |
| * See want to seek to the first frame but can't because |
| * the video has a negative duration. So we listen to the |
| * current time. When we get a time change so we must be at |
| * least the first frame so pause the video now and clean |
| * up the load state variables. |
| */ |
| private function videoPlayer_currentTimeChangeHandler(event:TimeEvent):void |
| { |
| //trace("videoPlayer_currentTimeChangeHandler: time = " + event.time); |
| |
| videoPlayer.removeEventListener(TimeEvent.CURRENT_TIME_CHANGE, videoPlayer_currentTimeChangeHandler); |
| videoPlayer.removeEventListener(MediaPlayerCapabilityChangeEvent.CAN_SEEK_CHANGE, videoPlayer_canSeekChangeHandler); |
| |
| videoPlayer.pause(); |
| videoPlayer.muted = beforeLoadMuted; |
| |
| if (videoPlayer.displayObject) |
| videoPlayer.displayObject.visible = true; |
| |
| inLoadingState1 = false; |
| inLoadingState2 = false; |
| inLoadingState3 = false; |
| |
| // Disable the TimeEvents again that we had |
| // enabled for loading a video while offstage. |
| if (!isNaN(oldCurrentTimeUpdateInterval)) |
| { |
| videoPlayer.currentTimeUpdateInterval = -1; |
| videoPlayer.bytesLoadedUpdateInterval = -1; |
| } |
| } |
| |
| /** |
| * @private |
| * |
| * Work around for negative durations - see FM-1009. |
| * |
| * If we get a duration event that is negative while in |
| * inLoadingState2 is true, then listen for the first time |
| * change event so we can pause the video. |
| */ |
| private function videoPlayer_durationChangeHandler(event:TimeEvent):void |
| { |
| //trace("videoPlayer_durationChangeHandler: time = " + event.time); |
| dispatchEvent(event); |
| |
| if (inLoadingState2) |
| { |
| if (event.time < 0) |
| { |
| // Work around for negative durations - FM-1009 |
| // We want to seek to the first frame but we can't because the |
| // duration of the video is reported as negative. As a work around, |
| // listen for the first time change event and then pause the video. |
| //trace("videoPlayer_durationChangeHandler: negative duration - wait for first current time change event"); |
| videoPlayer.addEventListener(TimeEvent.CURRENT_TIME_CHANGE, videoPlayer_currentTimeChangeHandler); |
| } |
| } |
| } |
| } |
| } |