/* ------------------------------------------------------------------

Options at creation-time:
  url: flv to play (also see playlist)
  repeat: default to true
  autoPlay: can be true/false or the number or ID of a clip to start with (defaults to false)
  playlist: array of clips; can be changed later
  width, height: (optional) integer or percentage only (no CSS units)
  autoBandwidthTest: defaults to true
  defaultBitrate: defaults to 400 (kbps)
  usableBandwidthFactor: only use this fraction of available bandwidth (default 0.75),
  swf: video player swf file (optional)

Callbacks:
  initHandler(): called when player is ready
  statusChangeHandler(status, num, clip): called on play, pause, etc.
  timeChangeHandler(seconds): called each second wile playing as time changes
  clipChangeHandler(num, clip, status): called only when the currently playing clip changes
  chapterChangeHandler(num, chapter): called when a new chapter in the current clip is reached

Public methods:
  scope(): returns jQuery object containing player HTML
  play([variant]): unpause, or play the given number, url, clip, or whatever's next
  next([num]): skip ahead one or more playlist items
  prev([num]): go back one or more playlist items
  pause(): pauses; use play() to resume
  stop(): stops
  seek(seconds): jump ahead to the given timecode; not always available
  message(text): display the given message; use message() to clear
  currentNum(): returns number of currently playing item in playlist
  currentClip(): returns clip object of currently playing item in playlist
  time(): returns seconds into currently playing item
  status(): returns current status (e.g. play, pause, stop, etc.)

Public properties:
  playlist: the array of clips to play

Example:
  
  var playerObject = null;
  $('#video_container').flvPlayer({
	  url: 'http://something.com',
	  initHandler: function() {
		  alert("loading player");
		  playerObject = this;
		  }
	  });
  
  // later on...
  playerObject.play('http://something.else.com');

------------------------------------------------------------------ */


function FLVPlayer_Clip(url) {
	var publ = this;
	
	var priv = {
		streams : [],
		chapters: [],
		lastChapterCache: -1
		};
	
	jQuery.extend(publ, {
		duration : null,
		metadata : {
			title : null,
			date : null,
			description : null,
			image: null
			},
		
		addStream : function(url, bitrate) {
			priv.streams.push({ 'bitrate' : bitrate, 'url' : url });
			return this;
			},
		
		hasStream : function(url) {
			for(var i = 0; i < priv.streams.length; i++)
				if(priv.streams[i].url == url) return priv.streams[i];
			return false;
			},
		
		chooseStream : function(availableBandwidth) {
			var best = null;
			var lowest = null;
			for(var i = 0; i < priv.streams.length; i++) {
				var s = priv.streams[i];
				if (s.bitrate <= availableBandwidth && (!best || s.bitrate > best.bitrate)) best = s;
				if (!lowest || s.bitrate < lowest.bitrate) lowest = s;
				}
			return (best || lowest).url;
			},
		
		addChapter : function(offset, metadata) {
			var i;
			for(i = 0; i < priv.chapters.length; i++) {
				if(offset < priv.chapters[i][0]) break;
				}
			priv.chapters.splice(i, 0, [ offset, metadata ]);
			},
		
		chapterDuration : function(n) {
			if(n >= priv.chapters.length) return null;
			if(n == priv.chapters.length-1) {
				return publ.duration ? publ.duration - priv.chapters[n][0] : null;
				}
			else {
				return priv.chapters[n+1][0]-priv.chapters[n][0];
				}
			},
		
		currentChapter : function(seconds) {
			if(seconds === null || seconds < 0) return -1;
 			if(priv.lastChapterCache && priv.lastChapterCache > 0 && priv.chapters[priv.lastChapterCache][0] < seconds && (priv.lastChapterCache == priv.chapters.length-1 || priv.chapters[priv.lastChapterCache+1] > seconds)) return priv.lastChapterCache;
			for(var i = 0; i<priv.chapters.length; i++) {
				if(priv.chapters[i][0] > seconds) {
					priv.lastChapterCache = i-1;
					return i-1;
					}
				}
			priv.lastChapterCache = priv.chapters.length-1;
			return priv.chapters.length-1;
			},
		
		numChapters : function() {
			return priv.chapters.length;
			},
		
		getChapter : function(n) {
			return priv.chapters[n];
			}
		
		});
	
	if(url) publ.addStream(url);
	}

jQuery.extend(FLVPlayer_Clip, {

	parseTime : function(time) {
		var parts = time.split(':').reverse();
		var seconds = parseFloat(parts.shift());
		if(parts.length) seconds += 60 * parseFloat(parts.shift());
		if(parts.length) seconds += 3600 * parseFloat(parts.shift());
		return parts.length ? NaN : seconds;
		},
	
	formatTime : function(seconds, longFormat) {
		var s = '' + Math.floor(seconds%60);
		if(s.length == 1) s = '0' + s;
		if(seconds < 60) return longFormat ? '0:00:'+s : '0:'+s;
		s = Math.floor(seconds/60)%60 + ':' + s;
		if(seconds < 3600) {
			if(longFormat) {
				return s.length < 5 ? '0:0'+s : '0:'+s;
				}
			else {
				return s;
				}
			}
		if(s.length == 4) s = '0' + s;
		s = Math.floor(seconds/3600) + ':' + s;
		return s;
		}

	});

(function() {
	jQuery.fn.flvPlayer = function(o) {
		return this.each(function() {
			new jQuery.flvPlayer(this, o);
			})
		};
	
	var playerCounter = 0;
	
	jQuery.flvPlayer = function(e, o) {
		var publ = this;
		jQuery.extend(publ, {
			scope : function() { return priv.scope },
			play : function(v) { return priv.play(v) },
			next : function(n) { return priv.next(n || 1) },
			prev : function(n) { return priv.next(-(n || 1)) },
			pause : function() { return priv.obj.flvplayer_pause() },
			stop : function() { return priv.obj.flvplayer_stop() },
			seek : function(secs) { return priv.obj.flvplayer_seek(secs) },
			message : function(str) { return priv.obj.flvplayer_message(str) },
			currentNum : function() { return priv.currentNum },
			currentClip : function() { return priv.currentClip },
			time : function() { return priv.time },
			status : function() { return priv.status },
			playlist : []
			});
		
		var priv = {
			o: {
				playlist: [],
				url: null,
				swf: '../swf/player.swf',
				autoPlay: false,
				width: '100%',
				height: '100%',
				initHandler: null,
				statusChangeHandler: null,
				timeChangeHandler: null,
				clipChangeHandler: null,
				chapterChangeHandler: null,
				autoBandwidthTest: true,
				defaultBitrate: 400,
				usableBandwidthFactor: 0.75, /* only applied to actual bandwidth, not defaultBitrate */
				repeat: true
				},
			
			scope: null,
			fnId: ++playerCounter,
			obj: null,
			currentNum : null,
			currentClip : null,
			time : null,
			status : null,
			lastNotifiedClip : null,
			lastNotifiedChapter : null,
			
			init: function(e, o) {
				if(o) jQuery.extend(priv.o, o);
				if(priv.o.playlist) {
					publ.playlist = priv.o.playlist;
					priv.o.playlist = null;
					}
				if(priv.o.url) {
					publ.playlist.push(new FLVPlayer_Clip(priv.o.url));
					priv.o.url = null;
					}
				
				window['flvplayer_load_'+priv.fnId] = priv.playerLoaded;
				window['flvplayer_status_'+priv.fnId] = priv.statusChanged;
				window['flvplayer_time_'+priv.fnId] = priv.timeChanged;
				window['flvplayer_initial_play_'+priv.fnId] = function() { priv.play(0) };
	
				priv.scope = jQuery(e);
				
				priv.scope.empty();
				var scopeId = priv.scope[0].id;
				if(!scopeId) priv.scope[0].id = scopeId = 'flvplayer_scope_'+priv.fnId;
				var objectId = 'flvplayer_object_'+priv.fnId;
				var so = new SWFObject($.absUrl(priv.o.swf), objectId, priv.o.width, priv.o.height, 8, '#FFF');
				// wmode-transparent and allowFullScreen cannot be on at the same time
//				so.addParam('wmode', 'transparent');
				so.addParam('allowFullScreen', 'true');
				so.addVariable('id', priv.fnId);
				so.write(scopeId);
				priv.scope.find('#'+objectId).addClass('flvplayer_object');
				priv.obj = priv.scope.find('#'+objectId)[0];
				
				},
			
			playerLoaded: function() {
				window.setTimeout(function() {
					jQuery(window).bandwidthTest(function(){
						publ.message('Testing connection speed');
						});
					jQuery(window).bandwidthTestComplete(function(){
						publ.message();
						});
					jQuery.bandwidth.setup();
					var testing = false;
					if(priv.o.autoBandwidthTest && !jQuery.bandwidth.available) {
						if(priv.o.autoPlay !== false) {
							jQuery(window).bandwidthAvailable(function(){
								priv.play(priv.o.autoPlay === true ? false : priv.o.autoPlay);
								});
							}
						jQuery.bandwidth.test();
						}
					else {
						if(priv.o.autoPlay !== false) {
							priv.play(priv.o.autoPlay === true ? false : priv.o.autoPlay);
							}
						}
					priv.callHandler(priv.o.initHandler, arguments);
					}, 0);
				},
			
			play: function(v) {
				if(!publ.playlist.length) return;
				if(v == parseInt(v)) {
					// jump to playlist item
					if(v < 0) v = publ.playlist.length - v;
					priv.currentNum = v % publ.playlist.length;
					priv.currentClip = publ.playlist[priv.currentNum];
					var url = priv.currentClip.chooseStream(jQuery.bandwidth.available ? jQuery.bandwidth.bandwidth * priv.o.usableBandwidthFactor : priv.o.defaultBitrate );
					priv.obj.flvplayer_play(url);
					priv.checkClipChange();
					}
				else if(!v) {
					if(priv.currentNum === null) {
						// start playing first clip
						priv.play(0);
						}
					else {
						// resume
						priv.obj.flvplayer_play();
						}
					}
				else if(v instanceof FLVPlayer_Clip) {
					// find/add this clip
					for(var i = 0; i < publ.playlist.length; i++)
						if(publ.playlist[i] === v) {
							priv.play(i);
							return;
							}
					publ.playlist.push(v);
					priv.play(publ.playlist.length-1);
					}
				else {
					// find/add this stream
					for(var i = 0; i < publ.playlist.length; i++) {
						var s = publ.playlist[i].hasStream(v);
						if(s || publ.playlist[i].metadata.id == v) {
							priv.play(i);
							return;
							}
						}
					publ.playlist.push(new FLVPlayer_Clip(v));
					priv.play(publ.playlist.length-1);
					}
				},
			
			next: function(n) {
				if(priv.currentNum === null) {
					priv.play(n < 0 ? publ.playlist.length - 1 : 0);
					}
				else {
					priv.play(priv.currentNum+n);
					}
				},
			
			checkClipChange: function() {
				if(priv.currentClip && priv.currentClip != priv.lastNotifiedClip) {
					priv.lastNotifiedClip = priv.currentClip;
					priv.lastNotifiedChapter = null;
					priv.callHandler(priv.o.clipChangeHandler, [ priv.currentNum, priv.currentClip, priv.status ]);
					}
				},
			
			statusChanged: function(status, url, duration) {
				priv.status = status;
				if(priv.currentClip && priv.currentClip.duration <= 0) priv.currentClip.duration = parseFloat(duration);
				priv.checkClipChange();
				priv.callHandler(priv.o.statusChangeHandler, [ status, priv.currentNum, priv.currentClip ]);
				if(status == 'end' && !(priv.o.repeat == false && priv.currentNum == publ.playlist.length - 1)) publ.next();
				},
				
			timeChanged: function(t) {
				priv.time = t;
				if(priv.currentClip) {
					var ch = priv.currentClip.currentChapter(t);
					if(ch != priv.lastNotifiedChapter) {
						priv.lastNotifiedChapter = ch;
						priv.callHandler(priv.o.chapterChangeHandler, [ ch, priv.currentClip.getChapter(ch) ]);
						}
					}
				priv.callHandler(priv.o.timeChangeHandler, [ t ]);
				},

			callHandler: function(handler, args, errorHandler) {
				if(!handler) return;
				if(!args) args = [];
				if(!errorHandler && window.console && window.console.log) errorHandler = function(e) { window.console.log(e) };
				try {
					handler.apply(publ, args);
					}
				catch(e) {
					if(errorHandler) errorHandler.apply(null, [ e ]);
					}
				}
			
			};
		
		priv.init(e, o);
		};
	})();

