Video Conference Flash Plugins
Welcome to a couple of really useful flash plugins, including source code,
in Flash / Adobe ActionScript (AS3):
1) Video Receive (download from sourceforge)
2) Video Publish (download from sourceforge)
These two plugins present the minimum functionality to access a webcam and
to access audio and/or video - live or recorded - from a Flash Media Server
(such as OSflash Red5, FMS, or rtmpy / tape
when it supports realtime RTMP). Version 0.3 is stable; Version 0.4
contains, in the receiver, an experimental (but useable) optional
playback "shuffle", along with playback start/stop/pause/resume buttons.
The plugins take, as "FlashVars" parameters, almost everything under the
sun that you will need to never have to get involved with Adobe
Flash Programming.
You can use these plugins to create video conferencing web sites; video
chat sites; your own YouTube; anything (that's the plan, anyway).
vidpublish.swf also works with audio-only: a webcam is optional.
The plugins are designed to be reasonably robust in the face of unreliable
internet connectivity. If the connection drops, the plugins will
automatically reconnect every few seconds.
Here is an example VideoChat site where these flash plugins are in use.
Please respect that the site is under development. Your input and
feedback would be much appreciated.
One of the most annoying things about flash-based videoconferencing sites
is insufficient handling of disconnects and errors. Frequently, an entire
site is based on Flash, and if the video connection drops out, and the
flash plugin has not been correctly designed, or if there is just a
problem with flash that requires a restart, you must exit the entire
Flash application.
This is unacceptable.
Many people lose their only line of communication (e.g. a chat window)
by performing a "reset" (restart browser, clear cache etc.) and so such
sites regularly lose customers, disillusioned with the technology.
Hence, these two plugins are designed to be stand-alone components where
it is anticipated that developers use them as part of a more robust
solution, based on HTML technology or even Desktop Application technology.
In this way, tried-and-tested HTML technology can act as a backup
to the more advanced Video/Audio Flash technology - for example
by coding your "chat" window in HTML / Javascript / AJAX.
Both KDE and GNOME desktops have flash stand-alone players: it is
anticipated that a desktop application could integrate these flash
plugins (using gtkmozembed) to provide video chat, video conferencing or
video publishing and playback for the desktop, with the distinct
possibility of being able to make the same application work on Windows
(if GTK or Mono were the chosen underlying desktop technology, for example).
However, much more sensibly, such is the ubiquitous nature of Flash,
that in combination with AJAX compiler technology such as
Google Wek Kit or Pyjamas, you can develop a site that
works on pretty much every single x86-based desktop computer in
existence, and when Gnash or SwfDec mature enough
and support webcams, that will expand to pretty much any type of
device with a CPU over about 500mhz.
Video Receive
receiveVideo.swf views a video stream. A mute button, stop stream
and volume control are provided. The mute and video stream buttons
toggle volume and video on and off, respectively.
receiveVideo.swf takes the following parameters, and you can see from
the example loader, receiveVideo.html, where they need to be added
and/or edited.
* server - used currently to create a stream (e.g. rtmp://server/oflaDemo)
* stream - name of the stream to be viewed
* user - name of the user's stream to be viewed (see explanation)
* width - width of the video window to be displayed (default: 160)
* height - height of the video window to be displayed (default: 120)
* windowwidth - width of the whole plugin (usually 30 pixels wider)
* windowheight - height of the whole plugin
* buffertime - audio-video stream buffer length, seconds (default: 1)
Optional extra parameters for version 0.4:
* showposition=1 - adds a slider to show and alter playback position
* recorded=1 - adds play/stop/pause/resume buttons
* live=1 - live streaming (default: live=1)
(These parameters must either be present or not present: if you do
not include any of them, "live" streaming is assumed. The position
slider works - except for "single click" is unreliable (on Linux
Flash 9.0) but when I checked other people's flash viewers which
show video content, they displayed exactly the same bug. So I
have every confidence that there isn't anything that can be done
about this, but it still leaves me with the responsibility to
release 0.4 as "experimental").
Please note that although the default width and height is 160x120, the
minimum required window width and height to fit the "Adobe Settings"
dialog box into the flash plugin window is 230x150. Anything less
than that, and users will NOT be able to run the dialog box that
allows them to enable access to the webcam or microphone! It is
therefore up to you - the web designer - to ensure that the user
has granted permission to access the webcam at least once, before
changing the window size down to below 230 x 150.
However, access to the webcam matters less for the receiver than
it does for the publisher: you may be able to get away with a
smaller "receive" window, but the users will still be very confused
as to why they cannot change the Adobe Flash settings.
Video Publish
vidpublish.swf sends a live video stream to an FMS server, to be
recorded or streamed as required. Volume control, Mute button and stop
stream controls are provided. The volume control allows a mild range
of microphone gain to be adjusted (between 80 and 100) due to bugs in
Flash 9.0 (when no webcam is present). The mute and video stream buttons
toggle volume and video on and off, respectively, without
interrupting the stream. If both buttons are toggled to "off",
the stream is still published (or recorded), so silence and
"black screen" will be viewed by recipients of the stream
(or viewers of the subsequent recording).
vidpublish.swf takes the following parameters. Again, you can
see how they can be used, by editing vidpublish.html.
* server - used currently to create a stream (e.g. rtmp://server/oflaDemo)
* stream - name of the stream to be viewed
* user - name of the user to be viewed (see explanation)
* streamtype - type of stream - "live" or "record" (default: live)
* width - width of the video window to be displayed (default: 160)
* height - height of the video window to be displayed (default: 120)
* recordwidth - width of the video to be recorded (default: 480)
* recordheight - height of the video to be recorded (default: 320)
* recordimmediately - start publishing video immediately (default: 1)
* fps - frames per second to be published (default: 7)
* bandwidth - maximum bytes allowed to be sent (default: 8192)
* quality - picture quality (default: 0)
* audiorate - audio capture rate (default: 22000 - 22khz)
* silencelevel - threshold for "silence" detection (default: 1)
* windowwidth - width of the whole plugin (usually 30 pixels wider)
* windowheight - height of the whole plugin
Please note that although the default width and height is 160x120, the
minimum required window width and height to fit the "Adobe Settings"
dialog box into the flash plugin window is 230x150. Anything less
than that, and users will NOT be able to run the dialog box that
allows them to enable access to the webcam or microphone! It is
therefore up to you - the web designer - to ensure that the user
has granted permission to access the webcam at least once, before
changing the window size down to below 230 x 150.
User instead of Stream
It has been observed that Red5, when live streams are connected
for prolonged periods of time, gets its knickers in a twist and
will not let you reconnect to a stream. Specifically, once a
"Receiver" has got hold of a stream, and a "Sender" is disconnected,
red5 will get into a state where "Senders" attempting to republish
under the same stream name cannot do so: an error is returned
indicating that the stream name is already in use.
As these plugins are intended to work without developers having
to get involved with editing ActionScript, a communications system
is required between the plugins and a red5 server.
As a workaround, receiveVideo.swf and vidpublish.swf call some
functions on the red5 server (see source code for more details)
called "getUserStreamName". The red5 server code must return
the name of the actual stream that the swf plugins connect to.
Each time that an error occurs (e.g. playback is interrupted),
the plugins will disconnect and call "getUserStreamName" again,
to obtain a new stream name.
Also, what is particularly useful - for web front-ends - is that
when vidpublish.swf has successfully published a stream, a
function called "userStreamConnected" is called. The red5 server
code can record this status information, for example in a database.
Web front-ends can then poll the database to find out whether
a user is successfully online. In partyliveonline.com,
the AJAX code displays a webcam icon with a link allowing users
to click on it, thus allowing the users to "view" only those
webcams which are actually successfully connected to the red5
server.
Source code used in the red5 server is here: modified oflademo
Use of the "user" parameter instead of (or as well as) the "stream"
parameter is entirely optional. You can ignore the "user" parameter
and use the "stream" parameter, without the enhancements provided
by the "getUserStreamName" function, and your users will just have
to put up with the very occasional inability to connect to your
system (especially after prolonged periods of use). Restarting
red5 periodically also fixes this issue, and also you can connect
to the admin panel of red5 and delete old streams.
Django Templates
For those people who like Django (which is very cool), and Pyjamas
(which is even more cool) here is some example usage / setup for these
plugins:
def vidrcv(request):
width = request.GET.get('width', 200)
height = request.GET.get('height', 150)
user = request.GET.get('user')
stream = request.GET.get('stream', "")
streamtype = request.GET.get('streamtype', "live")
buffertime = request.GET.get('buffertime', '0')
params = {'user': user,
'stream': stream,
'width': width,
'buffertime': buffertime,
'height': height,
'server': "rtmp://%s/oflaDemo" % server_name}
extraheight = 16
if streamtype == 'recorded':
params['recorded'] = 1
params['showposition'] = 1
extraheight = 32
return render_to_response('video/receiveVideo.html',
{'flashvars': urllib.urlencode(params),
'windowwidth': (int(width) + 30 + 16),
'windowheight': (int(height) + extraheight)})
def vidsend(request):
width = request.GET.get('width', 200)
height = request.GET.get('height', 150)
user = request.GET.get('user')
streamtype = request.GET.get('streamtype', 'live')
bandwidth = request.GET.get('bandwidth', '10240')
quality = request.GET.get('quality', '0')
fps = request.GET.get('fps', '7')
stream = request.GET.get('stream', "")
recordimmediately = request.GET.get('recordimmediately', 1)
params = {'user': user,
'width': width,
'height': height,
'recordimmediately': recordimmediately,
'recordwidth': 480,
'recordheight': 320,
'bandwidth': bandwidth,
'quality': quality,
'fps': fps,
'stream': stream,
'streamtype': streamtype,
'server': "rtmp://%s/oflaDemo" % server_name}
return render_to_response('video/vidpublish.html',
{'flashvars': urllib.urlencode(params),
'windowwidth': (int(width) + 30),
'windowheight': (int(height)),
})
You will need, in urls.py, to add something like this:
(r'^chat-service/video/view/*', 'chat.chatservice.views.vidrcv'),
(r'^chat-service/video/send/*', 'chat.chatservice.views.vidsend'),
You will need this and this, and to place them in your
templates/video directory (or to modify the render_to_response arguments,
above, as appropriate), and then, if you are using Pyjamas, subclass
Frame and do this for the sender:
class VideoFrame(Frame):
def __init__(self, chat, session_id, username, width=200, height=150, live=1):
Frame.__init__(self)
self.session_id = session_id
self.username = username
self.chat = chat
self.live = live
self.remote = ChatService()
self.chat.streamname = None
self.videowidth = width
self.videoheight = height
def start_stream(self):
if self.live:
self.streamtype = 'live'
else:
self.streamtype = 'record'
self.remote.create_stream(self.streamtype, self.live, self)
def activate_stream(self, streamname):
self.chat.streamname = streamname
# TODO: must record stream name with message, in database
url = '/chat-service/video/send/?user=%s' % self.username
url += '&streamtype=%s' % self.streamtype
url += '&width=%d' % self.videowidth
url += '&height=%d' % self.videoheight
if not self.live: # flash obtains stream name from username db lookup
url += '&stream=%s' % streamname
#url += 'recordimmediately=0' # give user chance to get ready
self.setUrl(url)
dlgwidth = self.videowidth + 30
dlgheight = self.videoheight
self.setWidth('%dpx' % dlgwidth)
self.setHeight('%dpx' % dlgheight)
def stream_ended(self):
self.remote.update_stream(self.chat.streamname,
None, # don't change description
self.live, False, self)
if hasattr(self.chat, "notifyStreamClosed"):
self.chat.notifyStreamClosed(self.chat.streamname)
def onRemoteResponse(self, response, request_info):
remote_method = request_info.method
if remote_method == 'create_stream':
if response:
self.activate_stream(response)
else:
Window.alert("Sorry, seems to be a connection issue. Are you logged in?")
And this for the receiver:
class VideoListener:
def __init__(self, user, link, stream=None, prerecorded=False,
width=200, height=150):
self.user = user
self.link = link
self.streamname = stream
self.width = width
self.height = height
self.prerecorded = prerecorded
def onClick(self, sender):
if (sender != self.link):
return
username = self.user.name
if ModalPopupActive(username):
return
idenfifier = username
url = "/chat-service/video/view/?user=%s" % username
url += '&width=%d' % self.width
url += '&buffertime=0'
if self.prerecorded:
url += '&streamtype=recorded'
url += '&height=%d' % (self.height - 32)
else:
url += '&height=%d' % self.height
if self.streamname:
url += "&stream=%s" % self.streamname
identifier = self.streamname
popup = Popup(identifier, self.user.get_name(), url)
left = self.link.getAbsoluteLeft() + 32
top = self.link.getAbsoluteTop() + 8
width = Window.getClientWidth()
height = Window.getClientHeight()
dlgwidth = self.width + 30
dlgheight = self.height
if left + dlgwidth > width - 10:
left = (width / 2) - (width - dlgwidth) / 2
if left + dlgwidth > width - 10:
left = 10
dlgwidth = width - 20
popup.set_width(dlgwidth)
popup.set_height(dlgheight)
popup.setPopupPosition(left, top)
popup.show()
The strange differences in the widths and heights are to allow the
extra options - at the side and bottom - the control widgets. So,
the arguments you pass in to VideoFrame and VideoListener are to
specify the size of the video / webcam.
You will also need this which provides the Popup class, above.
The function "ModalPopupActive()" is there to ensure that two popups with
the same identifier cannot be created: I use this as a double-check to
ensure that two "record" widgets do not get created by users accidentally
double-clicking, for example.
update_stream is a JSONrpc function, implemented in Django (JSONrpc using
Pimentech's libcommonDjango), and all it
does is mark the given stream as "online". stream_ended() gets called
when the VideoFrame is closed, which, by a rather circuitous route,
results in yet another JSONrpc call into the Django service which
marks the stream as "offline".
Another widget's job is to poll the Django service looking for streams
that are "online", and displaying a "webcam" icon with a link for
people to click on, activating a popup showing the videostream for
that user.
It's all really quite cool, written entirely in python, with the
front-end being compiled to AJAX/Javascript using Pyjamas, and the
back-end still written entirely in python, with Django. The only
component that's actually in Flash / Actionscript is the two flash
plugins.
Contracting
If you would like this code improved in any way, please contact me:
see my main site for contact details. The site is written
using, surprise-surprise, pyjamas - so if you use IE7, you are s***
out of luck, and so had better email me as lkcl@lkcl.net instead.