5.4. Playing Streams in Detail
The NetStream.play( )
method can be used to play live and recorded streams, segments of
recorded streams, and a playlist of streams. The play(
) method has a number of optional parameters that make
this possible:
in_ns.play(streamURI | false, [, start [,length [, flushPlaylists]]]);
The first parameter is required and must be either a relative URI
that identifies the stream to play or the value
false, which stops the current stream from
playing. The remaining parameters, shown inside square brackets, are
optional. The nesting of the square brackets indicates that if one
optional parameter is included, then so must all previous ones. For
example, to use the length parameter, the
start parameter must also be provided.
5.4.1. Playing Live or Recorded Streams
The start parameter can be used to
indicate more than its name
implies. The possible values for start are:
- -2
-
Plays the live stream at the specified URI or plays the recorded
stream at that location if the live stream is not found. If a
recorded stream is also not found, the NetStream
object waits for the stream to be published.
- -1
-
Plays only a live stream at the specified URI. If a live stream is
not found, the NetStream object waits for one to
be published. A recorded stream at the same URI will not be played.
- 0
-
Plays only a recorded stream at the specified URI from its beginning.
If a recorded stream is not found, an information object is returned
to NetStream.onStatus( ).
- Greater than 0
-
Same as passing 0, but indicates how many seconds into the recorded
stream to begin playing.
If a start value is not passed into the
play( ) method, the default is -2 (plays a live
or recorded stream).
The length parameter defines how many seconds the
live or recorded stream should play. For example, to play a stream
beginning 1 minute into the stream for 30 seconds, call
play( ) as follows:
in_ns = new NetStream(nc);
in_ns.play("announcements", 60, 30);
If the length parameter is -1 or omitted, the
stream plays until it ends. Note that, by definition, live streams
always start playing at the current time; only recorded streams can
be started at some offset. But the length of time to play a stream
can be limited even for live streams by specifying a time limit with
the length parameter.
5.4.1.1 Creating playlists
By
default, invoking play(
) overrides any previous play( )
command for that NetStream object, closing the
previous stream immediately and playing the new stream. However, you
can create a playlist in which the server treats a sequence of
recorded streams as a continuous stream; the server provides seamless
buffering so that there are no interruptions when the source stream
changes.
To add a stream to a playlist, pass false as the
fourth, optional parameter
(flushPlaylists)
when invoking play( ). This tells FlashCom not
to close the previous stream. Instead, the stream is queued and
buffered to play after the streams already in the playlist. Here is a
short example. Note that if flushPlaylists is
specified as true or omitted, as shown in the
first play( ) invocation, FlashCom reinitializes
the playlist:
in_ns = new NetStream(nc);
in_ns.play("announcements", 60, 30);
in_ns.play("live_Camera_1", -1, 15, false);
in_ns.play("live_Camera_2", -1, 15, false);
in_ns.play("prod_trailer", 0, -1, false);
The preceding code creates a playlist that plays the following
sequence:
30 seconds of the recorded stream named
announcements, starting 60 seconds into the
stream 15 seconds of the live_Camera_1 stream 15 seconds of the live_Camera_2 stream The entire recorded prod_trailer stream
The
flushPlaylists
parameter can also be used to control how ActionScript data is
streamed. See the "Stream logs"
section later in this chapter.
5.4.2. Stream Time
As a live or recorded stream plays, stream time
increases. If the stream pauses, stream time stops until the stream
begins to play again. Stream time is 0 at the beginning of a recorded
stream or when a live stream is first published. The stream time in
seconds is available from the NetStream.time
property. However, if no data is being received while a stream is
playing, the time property reflects the last
moment that data was received on the stream. When data arrives within
the stream, the time property jumps to the correct
value. For example, if a stream containing video plays for 30
seconds, at which point the video stops, the stream time will remain
at 30 until more data of some type arrives. If more data arrives
after another 20 seconds, the time value will
suddenly change to 50 and continue increasing as long as data
continues to arrive. Playlists are treated as though they are a
single stream. When each stream in the playlist begins, stream time
is not reset to 0. Therefore, the stream time is relative to when the
stream started, not when the current content started playing. For
example, if each playlist video clip is 30 seconds, the beginning of
the second clip will have a stream time of 30, not 0.
Internally, whenever data is attached to a stream by a publisher, a
timestamp is generated and embedded within the stream at the start of
the audio, video, or ActionScript message. The timestamp is used to
establish correct stream time when the stream is played. After a
period of time while a stream is playing but no audio, video, or
ActionScript data messages have arrived, the timestamp at the
beginning of the next message to arrive is used to update the stream
time.
5.4.3. Playback Events
The NetStream.onStatus(
) handler, if
defined, is
called when errors or
significant events occur. It receives an information object
containing five fields that describe the event:
level, code,
details, description, and
clientid. For example, when the chat movie
described at the beginning of this chapter first attempts to
subscribe to a stream named public/robert, the
information object passed to onStatus( )
contains the following property values:
info.level: status
info.code: NetStream.Play.Reset
info.description: Playing and resetting public/robert.
info.details: public/robert
info.clientid: 58996536
The level property will contain either
"error" or
"status", depending on the type of
event. The code property contains the most useful
message for determining what has happened. In this case, the stream
has been reset, which indicates that any other pending
play( ) commands have been dropped. The
details property contains the stream URI to which
the message relates.
When a NetStream object attempts to subscribe to
a live stream, its onStatus( ) handler will
normally be called twice and receive the info.code
values "NetStream.Play.Reset"
and "NetStream.Play.Start".
If the live stream is available, it will begin to play without
onStatus( ) being called again. However, the
stream may not have started publishing yet, in which case no data
will be received from FlashCom. The subscribing stream will therefore
wait without generating any further calls to onStatus(
). When the stream is finally published, the
onStatus( ) handler will be called a third time
and passed an information object with a code
property of "NetStream.Play.PublishNotify".
There is no notification if a live stream is not available the moment
an attempt is made to subscribe to it.
When a NetStream object attempts to connect to a
recorded stream without a start parameter or with
a start parameter of -2, it is not clear to
FlashCom whether the NetStream is attempting to play a live or
recorded stream. Consequently, even if the recorded stream does not
exist, the NetStream will behave as though it is attempting to play a
live stream. However, if a start parameter of 0 or
greater is passed into play( ), the NetStream
looks for a recorded stream only. In such a case, if the recorded
file is found, the onStatus( ) handler is called
twice with the code values
"NetStream.Play.Reset"
followed by "NetStream.Play.Start".
If the file is not found and start is 0 or
greater, FlashCom passes an information object with a
code value of
"NetStream.Play.StreamNotFound"
to the onStatus( ) handler.
The next few sections describe typical operational sequences and the
status events they generate. Later, under "Putting
the User in Control," we'll explain
how to use these events to modify the user interface dynamically to
help the user monitor and control various operations.
5.4.4. Playing MP3 Files
FlashCom 1.5 introduced the ability to stream recorded MPEG layer 3
(MP3) compressed audio files. MP3 files can be saved into a
subdirectory of an application's
streams subdirectory in order to make them
available for streaming. Once available, they can be played using a
stream URI that begins with the mp3: prefix. For
example, if an MP3 file is stored in the following file path:
- .../applications/radio/streams/_definst_/jazz/track1.mp3
and a NetConnection is established from a Flash
movie to the _definst_ instance of the
radio application, then the following statement
subscribes to a stream that plays the MP3 file:
in_ns.play("mp3:jazz/track1", 0, -1);
MP3 files
can contain tagged information in the ID3 format that may contain
information such as the song title, artist, album, year, genre, and a
comment. To read the ID3 information, use a separate play(
) statement in which the relative URI of the stream begins
with the id3: prefix. For example:
in_ns.play("id3:jazz/track1");
To actually read the ID3 data, you must define a
NetStream.onId3( ) handler. Example 5-4 shows a short demonstration script that reads
the ID3 information, writes it into the Flash development
environment's Output panel, and then starts to play
the audio stream. The ID3 tag names and values are returned as the
property
names and values of the information object passed into the
onId3( ) handler.
Example 5-4. Reading the ID3 tags and playing MP3 audio using one stream
NetStream.prototype.onId3 = function (info) {
trace("NetStream.onId3>");
for (var p in info) {
trace(" " + p + ": " + info[p]);
}
};
nc = new NetConnection( );
nc.connect("rtmp:/radio");
ns = new NetStream(nc);
ns.setBufferTime(10);
ns.play("id3:jazz/track1");
ns.play("mp3:jazz/track1", 0, -1, false);
In order to make sure that all the ID3 information is received before
the audio is played, the code sets the
flushPlaylists
parameter to false when play(
) is called a second time. Two separate streams can also
be usedone to retrieve the ID3 information and another to play
the audio.
FlashCom 1.5 returns the code value of
"NetStream.Play.Failed",
not "NetStream.Play.StreamNotFound",
when an MP3 file is not found.
5.4.5. Uploading Prerecorded Streams
Prerecorded media files in both FLV and MP3
format can be uploaded to a FlashCom Server where they can be played.
FLV files that are not recorded by the FlashCom Server can be created
using Sorenson Squeeze for Flash MX 2004 or other utilities, such as
Squeeze Lite, which is included with the Macromedia Video Kit for
Dreamweaver. There are two options as to where FLV and MP3 files can
be stored on the server for playback. (FLV files recorded by FlashCom
can also be copied to other locations on the server where they can be
played by other instances.) One option is to place FLV or MP3 files
in the
streams/instanceName
subdirectory of an application's home directory
where instanceName is an instance name
such as _definst_. However, the files are
available only for that instance of the application. The second
option is to specify a virtual directory so that every
instance of every application can access the same files. A virtual
directory is a pathname that is mapped to an actual directory path.
The mapping is defined within the
<Streams> tag of a
Vhost.xml configuration
file. For example, to map the common name music to
the path C:\CommonFiles\music, use the following
<Streams> tag:
<Streams>music;C:\CommonFiles\music</Streams>
A semicolon must separate the virtual pathname and the actual
directory path. If multiple mappings are required, use multiple
<Streams> tags.
5.4.6. Buffering When Subscribing
Live streams
that support real-time
chats and video conferences should not be buffered. Any
delay
due to buffering of a stream will only make conversation more
difficult. The amount of lag, or delay, in seconds of a live stream
can be determined by checking the
NetStream.liveDelay property. For recorded
streams, or even live streams that are not part of a real-time
conversation, buffering can be used to provide a more seamless and
higher-quality playback than live bufferless streaming provides.
When recorded streams are played, a small default buffer time of 10
milliseconds (0.01 second) is used even if the buffer time is set to
0. If stream data arrives more slowly than it is being played,
eventually the buffer will empty and playing will stop until the
buffer refills. With a fast connection and low data rates, this may
never happen. However, if data arrives too slowly to be continuously
played, the stream may start and stop repeatedly. Setting the buffer
time to a higher value will guarantee that longer sections of a
stream play continuously.
If a recorded stream is played and then the same
NetStream object is used to play a live stream,
a buffer time will still be set on the stream. Clear it by setting
the buffer time to 0.
To set the buffer time for a stream, use
NetStream.setBufferTime( ):
in_ns.setBufferTime(10);
To determine the number of seconds of data currently in the buffer,
use the NetStream.bufferLength property. During
testing, you can watch the buffer length change with a simple test
script that displays the length in a text field:
in_ns.play(streamName);
in_ns.setBufferTime(10);
function onEnterFrame( ) {
bufferLength_txt.text = in_ns.bufferLength;
}
The bufferLength property is often used to set the
size of a progress bar after buffering begins. In fact, it is often a
good idea to display a movie clip that shows a progress bar whenever
the buffer is empty until the buffer refills and then hide the
progress bar. When a stream stops, the user will see the progress bar
gradually increase until it reaches 100%, and then it will disappear
when the stream starts to play again. An exception occurs if the
stream is buffering very near the end. It may not need to completely
fill the buffer in order to play to the end. One way to determine
when to show or hide a buffering progress indicator movie clip is to
watch for three buffer-related code values in the
information objects passed to onStatus( ),
namely "NetStream.Buffer.Empty",
"NetStream.Buffer.Full",
and "NetStream.Play.Stop".
"NetStream.Play.Stop" indicates
that buffering is complete when the stream is near the end and does
not need to fill the buffer completely before starting to play.
5.4.7. Detecting the End of the Stream
When the publisher of a live
stream stops publishing a stream, the
onStatus( ) method of any subscribing
NetStream objects are called and passed an
information object with a code value of
"NetStream.Play.UnpublishNotify".
So there is no ambiguity about when a live stream ends. However, when
a recorded stream is being played, two things happen. First, the
server stops sending data to the Flash movie. Second, any data still
in the stream buffer continues playing within the Flash movie until
the buffer is empty. When the server stops sending data to the movie,
onStatus( ) is passed a code
value of "NetStream.Play.Stop".
Then, when the buffer empties shortly afterward, onStatus(
) is called again with a code value of
"NetStream.Buffer.Empty". The
following output shows the information objects'
code values during playback of a stream from start
to finish:
NetStream.Play.Reset
NetStream.Play.Start
NetStream.Buffer.Full
NetStream.Buffer.Empty
NetStream.Buffer.Full
NetStream.Play.Stop
NetStream.Buffer.Empty
Someone viewing this stream does not see or hear it start to play
until the first
"NetStream.Buffer.Full"
value occurs. The stream stops playing when the first
"NetStream.Buffer.Empty" value is
returned and does not start again until the second
"NetStream.Buffer.Full" value is
returned. Finally, the stream stops playing completely after the last
"NetStream.Buffer.Empty"
value occurs.
5.4.8. Seeking Within a Stream
When a recorded
stream is being played,
NetStream.seek( ) can be used to jump to any
time within the recorded stream and begin playing the stream from
that point. The seek( ) method takes one required
parameterthe number of seconds from the beginning of the
stream to seek to. For example, this statement stops the playing
stream and restarts it 20 seconds from the beginning of the recorded
stream:
in_ns.seek(20);
Calling seek( ) empties the buffer and restarts
buffering of the stream. Consequently, the following
code values will be passed in three successive
onStatus( ) handler calls:
NetStream.Seek.Notify
NetStream.Play.Start
NetStream.Buffer.Full
The stream will not visibly or audibly begin to play again until the
"NetStream.Buffer.Full"
code value is received. If the seek cannot be
carried out, a code value of
"NetStream.Seek.Failed" will be
returned.
To seek to another time in the stream relative to the current stream
time, convert it to absolute time by adding the
NetStream.time property as an offset. For example,
to seek to 5 seconds before the current time, subtract 5 from the
current time to get the absolute time:
relativeSeekTime = -5;
in_ns.seek(in_ns.time + relativeSeekTime);
Compressed video data is almost always made
up of keyframes separated by difference frames. Most recorded
video frames are therefore not full
video frames but rather updates from the last keyframe. When seeking
within a stream, the requested time usually does not fall on a
keyframe. When this happens, FlashCom's default
behavior is to start playing the stream from the closest preceding
keyframe. The alternative is to have FlashCom seek to the previous
keyframe, read the difference frames up until the seek time, and
construct a new video frame dynamically. This process is called
enhanced seeking and can be enabled using an
Application.xml
file's <EnhancedSeek> flag.
By default, enhanced seeking is disabled because it is a more
computationally expensive operation. To turn on enhanced seek for one
application, place an Application.xml file in
the home directory of an application with the following tag:
<EnhancedSeek>true</EnhancedSeek>
Using enhanced seeking for all applications is not recommended
because it requires the server to perform the extra work of reading
the preceding frames to create the current frame. For a complete
description of the
Application.xml and other XML
configuration files, consult Managing Flash Communication
Server, available at:
- http://www.macromedia.com/support/flashcom/documentation.html
5.4.9. Pausing and Unpausing a Stream
The NetStream.pause( )
method can be used to pause and unpause playback of a recorded
stream. When called without any parameters, it toggles between
pausing and playing the
stream. Calling
pause(true) pauses the stream; calling
pause(false) resumes playback. Unfortunately,
when a stream is paused, the stream's buffer
empties, so that when the stream is unpaused, the stream must refill
the buffer before it can begin playing again. When a stream is paused
and unpaused, NetStream.onStatus( ) is called
four times. The information objects'
code values will be:
NetStream.Pause.Notify
NetStream.Unpause.Notify
NetStream.Play.Start
NetStream.Buffer.Full
After the "NetStream.Buffer.Full"
code value is received, the stream will appear to play to the user.
If seek( ) is called while a stream is paused,
the current stream time changes to reflect the seek(
) request and the video frame at the seek time is
displayed. When the stream is unpaused, the stream plays starting
from the new time. In such a case, the information
objects' code values received in
succession by onStatus( ) are:
NetStream.Pause.Notify
NetStream.Seek.Notify
NetStream.Play.Start
NetStream.Unpause.Notify
NetStream.Play.Start
NetStream.Buffer.Full
In between "NetStream.Pause.Notify"
and "NetStream.Unpause.Notify", the
"NetStream.Seek.Notify" code
indicates the seek operation succeeded and
"NetStream.Play.Start" indicates
that the server sent data to the subscriber. No buffer-related
message occurs between the pause codes.
5.4.10. Putting the User in Control
A common task in developing Flash communication applications is
creating an audio/video stream player with volume, mute, pause, stop,
start, and seek/progress controls and a pop-up buffering indicator.
It is also desirable to visibly disable and enable controls or
otherwise change their text or appearance depending on the state of
an application. To make everything work properly, an application has
to know the current state of the stream, be informed when it changes,
and be able to track requests made of a
NetStream object. Most of the work of
interpreting what a stream is doing begins in the onStatus(
) handler. The information objects that are passed into
onStatus( ) provide most of the low-level
information about a stream; however, they do not describe the state
of the stream at the level an application may need. For example, in
order to know when to pop up a movie clip that shows the buffering
status of the stream, we need to know when the stream has paused in
order to refill its buffer. No single information object delivered to
the onStatus( ) handler indicates when the
stream has paused to refill its buffer. So, before looking at writing
a complete onStatus( ) handler,
let's look at some common problems in writing one
designed to help a stream playback application determine what a
stream is doing.
5.4.10.1 NetStream.Buffer.Empty
When the code property of an information object
indicates the buffer is empty, either the stream has played through
to the end, or the stream has simply run out of data and must pause
to refill the buffer. How do you tell the difference? A first good
answer starts with the recognition that, when the stream has played
to the end, the "NetStream.Play.Stop"
message precedes the "NetStream.Buffer.Empty"
one. We might try to differentiate between the two states by
introducing a stopped property to act as a flag as
follows:
in_ns.onStatus = function (info) {
writeln(info.code);
switch (info.code) {
case "NetStream.Play.Stop":
this.stopped = true;
break;
case "NetStream.Buffer.Empty":
if (this.stopped) {
this.reportChange("playingAtEnd");
}
else {
this.reportChange("buffering");
}
break;
case "NetStream.Buffer.Full":
this.reportChange("playing");
break;
}
};
In this code snippet, a function named reportChange( )
is passed a string that indicates the current state of the
stream. In turn, reportChange( ) will pass on
the message to another object that is responsible for controlling the
streamsomething I'll discuss later. For now,
we just want to properly inform some other object of what the stream
is doing. Now imagine that the user watches the stream through to the
end so that "NetStream.Play.Stop"
and "NetStream.Buffer.Empty"
code values are passed to onStatus(
). The onStatus( ) handler will
correctly report that the stream has played through to the end. Now,
what happens if the user restarts the stream? Whenever the buffer
empties from now on, onStatus( ) remembers that
it has seen a "NetStream.Play.Stop"
code and incorrectly reports that the stream has played to the end.
The solution is to add another case clause to
reset the stopped flag when the stream plays:
case "NetStream.Play.Start":
this.stopped = false;
break;
5.4.10.2 NetStream.Seek.Notify
When a seek occurs while a stream is playing, the buffer is emptied
and the stream stops until the seek is complete and the buffer is
refilled. However, there is no
"NetStream.Buffer.Empty" message to
indicate the buffer is empty in this situation. So, the
"NetStream.Seek.Notify"
message must serve the same purpose. But there is a complication! If
the stream is paused when the user seeks to a new location, the
stream does not start to fill the buffer (the buffer fills only when
the stream is playing). So just as an empty buffer message may mean
more than one thing, the notify message alone does not indicate the
state of the stream. What to do? Again we can resort to tracking
whether the stream is paused by setting a paused
property, which we add to the NetStream object.
We can therefore add case clauses as follows:
case "NetStream.Seek.Notify":
if (!this.paused) {
this.reportChange("buffering");
}
break;
case "NetStream.Pause.Notify":
this.paused = true;
this.reportChange("paused");
break;
case "NetStream.Unpause.Notify":
this.paused = false;
break;
With six case clauses in our
switch statement so far, you may wonder how far
we have to go to write an onStatus( ) handler
that will reliably describe what a NetStream is
really doing while subscribed to a recorded stream. The answer is
that it depends on what your application needs to do. But rather than
writing numerous onStatus( ) handlers for
different applications and run the risk of mishandling some events,
you should write one onStatus( ) handler that
handles all the events related to playing a recorded stream. In fact,
for convenience, a good way to do this is to create a
NetStream subclass that can be used in many
different applications. Example 5-5 shows one way to
create a NetStream subclass using
ActionScript 1.0-style syntax.
Example 5-5. A NetStream subclass for use in subscribing to recorded streams
function NetStreamPlaybackClass (nc) {
this.stopped = false;
this.paused = false;
super(nc);
}
NetStreamPlaybackClass.prototype = new NetStream( );
NetStreamPlaybackClass.prototype.play = function (streamURI, start,
length, flushPlaylists) {
// Preprocess parameters if necessary.
super.play.apply(this, arguments);
};
NetStreamPlaybackClass.prototype.pause = function (flag) {
// Preprocess parameters if necessary.
if (flag != undefined) {
this.paused = flag;
}
else {
this.paused = !this.paused;
}
super.pause.apply(this, arguments);
// Notify immediately.
if (this.paused) {
this.onStatus({code: "NetStream.Pause.Notify", level: "status"});
}
};
NetStreamPlaybackClass.prototype.reportChange = function (state) {
this.state = state;
this.obj[this.method](state, this);
};
NetStreamPlaybackClass.prototype.onStatus = function (info) {
this.lastInfo = info;
if (info.level != "status") {
this.reportChange(info.level);
return;
}
switch (info.code) {
case "NetStream.Play.Start":
if (!this.paused)
this.reportChange("buffering");
this.stopped = false;
break;
case "NetStream.Play.Stop":
if (this.bufferLength == 0) {
this.reportChange("playingAtEnd");
}
else {
this.reportChange("playing");
}
this.stopped = true;
break;
case "NetStream.Buffer.Empty":
if (this.stopped) {
this.reportChange("playingAtEnd");
}
else {
this.reportChange("buffering");
}
break;
case "NetStream.Buffer.Full":
this.reportChange("playing");
break;
case "NetStream.Seek.Notify":
if (!this.paused) {
this.reportChange("buffering");
}
else {
this.reportChange("seeking");
}
break;
case "NetStream.Pause.Notify":
this.paused = true;
this.reportChange("paused");
break;
case "NetStream.Unpause.Notify":
this.paused = false;
break;
}
};
// setChangeHandler( ) must be passed a method and an object.
NetStreamPlaybackClass.prototype.setChangeHandler = function (method, obj) {
this.method = method;
this.obj = obj;
};
NetStreamPlaybackClass.prototype.getLastInfo = function ( ) {
return this.lastInfo;
};
The NetStreamPlaybackClass subclass defined in
Example 5-5 operates behind the scenes but does not
directly affect an application's UI. On its own, it
does not enable or disable buttons or move sliders; instead, it tells
an object that registers itself as a change handler what the stream
is doing. To tie together button components, a custom slider, or
other interface elements with
NetStreamPlaybackClass, a good design choice is
to create an object that acts as a mediator (sometimes called a
director) between the interface controls and a
NetStream object or
NetStream subclass. To be effective, a mediator
object must register itself as the change handler for all the
interface elements and for the
NetStreamPlaybackClass instance. When a change
notification arrives from another object, the mediator must determine
how to respond by manipulating the stream or interface elements. This
design has the advantage that all state management code is
centralized in one place. Example 5-6 shows how a
mediator object works with two movie
clipsbuffer_mc, a clip that shows a
progress bar, and seek_mc, a clip that contains a
slider. The example shows a partial listing for a mediator class
named
PlayBackDirector and some of the code used to connect it
to other objects. The complete code for this example is
available on the book's web site.
Example 5-6. Partial listing of the PlayBackDirector class
function PlayBackDirector ( ) {
this.soundOn = true;
this.lastStateName = "";
}
PlayBackDirector.prototype.playing = function (obj) {
// Hide the buffer status movie clip.
delete buffer_mc.onEnterFrame;
buffer_mc._visible = false;
// Start tracking the stream time.
seek_mc.onEnterFrame = function ( ) {
this.setPosition(stream.time);
}
// Set the buttons to the right state.
play_pb.setEnabled(true);
play_pb.setLabel("Stop");
pause_pb.setEnabled(true);
pause_pb.setLabel("Pause");
};
PlayBackDirector.prototype.buffering = function (obj) {
// Show the buffer status movie clip.
buffer_mc.setMaxValue(stream.bufferTime)
buffer_mc.onEnterFrame = function ( ) {
this.setPosition(stream.bufferLength);
}
buffer_mc._visible = true;
// Stop tracking the stream time.
delete seek_mc.onEnterFrame;
// Set the buttons to the right state.
play_pb.setEnabled(true);
play_pb.setLabel("Stop");
pause_pb.setEnabled(true);
pause_pb.setLabel("Pause");
};
PlayBackDirector.prototype.onStreamChange = function (stateName, obj) {
if (stateName == this.lastStateName) return;
this.lastStateName = stateName;
this[stateName](obj);
};
// This handler calls seek( ) on the stream and disables the slider until
// the stream starts to play again.
PlayBackDirector.prototype.onSliderChange = function (pos, obj) {
delete seek_mc.onEnterFrame;
stream.seek(pos);
};
PlayBackDirector.prototype.onPlayRequest = function (btn) {
if (btn.getLabel( ) == "Play") {
this.playing(stream);
stream.play("public/Ryerson_High_Speed");
}
else {
stream.close( );
btn.setLabel("Play");
delete seek_mc.onEnterFrame;
delete buffer_mc.onEnterFrame;
buffer_mc._visible = false;
pause_pb.setEnabled(false);
}
};
The onStreamChange( ) method is called whenever
the state of the stream object changes. For example, if
onStreamChange( ) is passed a
stateName value of
"buffering",
PlayBackDirector.buffering( ) is called. The
buffering( ) method is responsible for setting
everything controlled by PlayBackDirector to the
correct state when the stream is buffering. The
buffer_mc clip's range is
adjusted to reflect the stream buffer's time, an
onEnterFrame( ) method is created for the clip
so that on each frame it will show the current buffer length, and
finally, the buffer_mc clip is made visible. On
the other hand, if a "playing"
message arrives, the buffer_mc clip is hidden and
its onEnterFrame( ) method is deleted. Ideally,
there should be one method for each stream state, which guarantees
that all the objects and movie clips controlled by
PlayBackDirector always respond appropriately to
changes in stream state.
 |
As of Flash Player 6.0.79.0,
"NetStream.Buffer.Empty"
events are not always generated when a stream pauses to buffer before
continuing to play. A version of
NetStreamPlaybackClass is provided on the
book's web site that fixes the problem by regularly
checking whether the stream is playing and calling the
onStatus( ) method with a
"NetStream.Buffer.Empty" message if
it has paused to buffer. Macromedia expects to fix this for Flash
Player 8, due to be released in the second half of 2005.
|
|
5.4.11. Managing Bandwidth
Matching the size of the data stream to the
client's available bandwidth is the most important
factor in determining how well a stream plays for any user. FlashCom
cannot further compress a stream to suit the bandwidth available for
each client or interpolate video frames to lower resolutions to cope
with lower-bandwidth connections. As will be discussed in Chapter 7, the best way to tailor a stream to
different bandwidths is to create separate streams for typical
connection speeds. Often this means creating one stream for modem
users, another for DSL users, and possibly one for users on a LAN.
However, once a stream is selected for playing by a client, there are
still some things beyond buffering that can be done to improve
playback
performance when
bandwidth is less than optimal.
5.4.11.1 Shutting off audio or video
The receiveAudio( )
and receiveVideo( )
methods of a NetStream object
can be used to stop and start the server sending audio or video in a
stream. Both methods are passed a Boolean value that determines
whether audio or video data should be sent by the server. Example 5-7 shows a method that is
called when an audio
Mute button is clicked.
Example 5-7. A method for toggling sound on and off
PlayBackDirector.prototype.onMuteAudioRequest = function (obj) {
this.soundOn = !this.soundOn;
stream.receiveAudio(this.soundOn);
if (this.soundOn) {
muteAudio_pb.setLabel("Mute Audio");
}
else {
muteAudio_pb.setLabel("Restore Audio");
}
};
When a stream is already playing and the audio is muted by
instructing the server to stop sending audio, the audio will not stop
playing in the client immediately if there is still audio data in the
stream's buffer. Similarly, if video data is in a
buffer when a request to restart sending audio data is received, it
may be some time before audio is heard on the client system. Stopping
the server from sending video using
receiveVideo(false) may improve stream playback
performance. Without the need to download and buffer video data,
audio and ActionScript data can fill the buffer faster, leading to
fewer pauses for buffering.
5.4.11.2 Capping client bandwidth usage
The maximum bandwidth a client can use to send or receive data is
capped by the server according to the
<Bandwith>,
<ServerToClient>, and
<ClientToServer> tags in the
Application.xml file
that controls an application. The default settings allow a client to
send 250,000 bytes/sec (244 KB/s) to the server as well as to receive
250,000 bytes/sec. A client connected via modem is likely to be able
to send and receive much less dataoften less than 4000
bytes/second. When a client is subscribed to a stream, FlashCom
attempts to monitor network traffic and drops video frames as
necessary. To help the server determine the bandwidth available, each
client's
bandwidth limit can be set using
Client.setBandwidthLimit( ), as shown in Example 5-8.
Example 5-8. Server-side script that relies on a connection type string being passed to the server from the client when it connects
connectionSettings = {
modem: {up: 4000, down: 4000},
dsl: {up: 16000, down: 62000},
lan: {up: 1250000, down: 10000000}
}
application.onConnect = function (client, connectionType) {
var setting = connectionSettings[connectionType];
if (setting) {
client.setBandwidthLimit(setting.up, setting.down);
}
return true;
};
5.4.11.3 Conferences and bandwidth
Live conferences in which users see and hear
one another simultaneously can create a heavy processing load on each
Flash movie and the FlashCom Server hosting the conference, and tax
the bandwidth available to the client and server. The processing and
bandwidth load for each client scales linearly with the number of
participants. The load and bandwidth demands on the server increase
as the square of the number of clients.
Figure 5-1
shows the outgoing stream from each client and the incoming streams
for small conferences of two or three participants.

Each client must send one stream to the conference instance and
receive as many streams as there are other participants. In other
words, if there are n participants, each will
have to send one and receive n - 1 streams:
Total client streams = 1 + n - 1 = n
The server will receive a stream from each client and send out to
each client one stream from all the other clients. Therefore, the
total number of streams handled by the server will be:
Total server streams = n * (1 + n - 1) = n2
Conferences with too many participantsall sending and
receiving audio and videocan quickly swamp some clients with
data and may contribute to bandwidth and load problems on the server.
There is no magic solution to this problemit is the nature of
live conferences. When designing and hosting conference systems,
consider making sure that clients do not attempt to send more audio
and video data than absolutely necessary, that there is a limit on
the number of participants, or that a token passing system is used to
restrict the number of clients actually sending audio and video at
one time. Assuming each
video stream
requires 8 KB/s, Table 5-1 shows how bandwidth
demand increases per client connection.
Table 5-1. Bandwidth demands for number of client connections|
Number of clients
|
Client upstream demand
|
Client downstream demand
|
Server demand
|
|---|
|
2
|
8 KB/s
|
8 KB/s
|
32 KB/s
| |
3
|
8 KB/s
|
16 KB/s
|
72 KB/s
| |
4
|
8 KB/s
|
24 KB/s
|
128 KB/s
| |
5
|
8 KB/s
|
32 KB/s
|
200 KB/s
| |
10
|
8 KB/s
|
72 KB/s
|
800 KB/s
| |
20
|
8 KB/s
|
152 KB/s
|
3.2 MB/s
| |
50
|
8 KB/s
|
392 KB/s
|
20 MB/s
|
 |