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 will be made available later:
	right now, it is available on request.

	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.