--- C:\Users\jpeck\AppData\Local\Temp\ClientBroadcastStream.java-rev620.svn000.tmp.java	2009-03-27 15:10:28.000000000 -0700
+++ C:\Users\jpeck\AppData\Local\Temp\ClientBroadcastStream.java-rev621.svn000.tmp.java	2009-03-27 15:10:35.000000000 -0700
@@ -96,13 +96,13 @@
 	/**
 	 * Logger
 	 */
 	private static final Logger log = LoggerFactory.getLogger(ClientBroadcastStream.class);
 
 	/** Stores absolute time for video stream. */
-	private int audioTime = -1;
+	private int audioTime = 0;
 
 	/**
 	 * Total number of bytes received.
 	 */
 	private long bytesReceived;
 
@@ -129,13 +129,13 @@
 	/**
 	 * Timestamp the stream was created.
 	 */
 	private long creationTime;
 
 	/** Stores absolute time for data stream. */
-	private int dataTime = -1;
+	private int dataTime = 0;
 
 	/** Stores timestamp of first packet. */
 	private int firstPacketTime = -1;
 
 	/**
 	 * Pipe for live streaming
@@ -185,13 +185,16 @@
 	/**
 	 * Factory object for video codecs
 	 */
 	private VideoCodecFactory videoCodecFactory = null;
 
 	/** Stores absolute time for audio stream. */
-	private int videoTime = -1;
+	private int videoTime = 0;
+
+	private int minStreamTime = 0;
+	public void setMinStreamTime(int mst) { minStreamTime = mst; }
 
 	/** Listeners to get notified about received packets. */
 	private Set<IStreamListener> listeners = new CopyOnWriteArraySet<IStreamListener>();
 	
 	/**
 	 * Check and send notification if necessary
@@ -253,21 +256,50 @@
 			rtmpEvent = (IRTMPEvent) event;
 		} catch (ClassCastException e) {
 			log.error("Class cast exception in event dispatch", e);
 			return;
 		}
 		int eventTime = -1;
-		// If this is first packet save it's timestamp
-		if (firstPacketTime == -1) {
-			firstPacketTime = rtmpEvent.getTimestamp();
+		if (log.isDebugEnabled()) {
+			// If this is first packet save its timestamp; expect it is absolute? no matter: it's never used!
+			if (firstPacketTime == -1) {
+				firstPacketTime = rtmpEvent.getTimestamp();
+				log.debug(String.format("CBS=@%08x: firstPacketTime=%d %s"
+										,System.identityHashCode(this)
+										,firstPacketTime
+										,(rtmpEvent.getHeader().isTimerRelative()?"(rel)":"(abs)")
+										));
+			}
+
+			int basetime = ((rtmpEvent instanceof VideoData) ? videoTime
+							: ((rtmpEvent instanceof AudioData) ? audioTime : dataTime));
+			if (rtmpEvent.getHeader().isTimerRelative()) {
+				int abstime = rtmpEvent.getTimestamp() + basetime;
+				log.debug(String.format("CBS=@%08x: rtmpEvent=%s  timestamp=%d (relative)+%d = %d"
+										,System.identityHashCode(this)
+										,rtmpEvent.getClass().getSimpleName()
+										,rtmpEvent.getTimestamp()
+										,basetime
+										,abstime));
+			} else {
+				int deltime = rtmpEvent.getTimestamp() - basetime;
+				log.debug(String.format("CBS=@%08x: rtmpEvent=%s  timestamp=%d (absolute)-%d = %d"
+										,System.identityHashCode(this)
+										,rtmpEvent.getClass().getSimpleName()
+										,rtmpEvent.getTimestamp()
+										,basetime
+										,deltime));
+			}
 		}
 		if (rtmpEvent instanceof AudioData) {
 			if (info != null) {
 				info.setHasAudio(true);
 			}
 			if (rtmpEvent.getHeader().isTimerRelative()) {
+				if (audioTime == 0)
+					log.warn("First Audio timestamp is relative!"+rtmpEvent.getTimestamp());
 				audioTime += rtmpEvent.getTimestamp();
 			} else {
 				audioTime = rtmpEvent.getTimestamp();
 			}
 			eventTime = audioTime;
 		} else if (rtmpEvent instanceof VideoData) {
@@ -289,26 +321,50 @@
 			}
 
 			if (info != null) {
 				info.setHasVideo(true);
 			}
 			if (rtmpEvent.getHeader().isTimerRelative()) {
+				if (videoTime == 0)
+					log.warn("First Video timestamp is relative!"+rtmpEvent.getTimestamp());
 				videoTime += rtmpEvent.getTimestamp();
 			} else {
 				videoTime = rtmpEvent.getTimestamp();
+				// Flash player may send first VideoData with old-absolute timestamp.
+				// This ruins the stream's timebase in FileConsumer.
+				// We don't want to discard the packet, as it may be a video keyframe.
+				// Generally a Data or Audio packet has set the timebase to a reasonable value,
+				// Eventually a new/correct absolute time will come on the video channel.
+				// We could put this logic between livePipe and filePipe;
+				// This would work for Audio Data as well, but have not seen the need.
+				int cts = Math.max(audioTime, dataTime);
+				cts = Math.max(cts, minStreamTime);
+				int fudge = 20;
+				// accept some slightly (20ms) retro timestamps [this may not be needed,
+				// the publish Data should strictly precede the video data]
+				if (videoTime + fudge < cts) {
+					if (log.isInfoEnabled()) {
+						log.info(String.format("dispatchEvent: adjust archaic videoTime, from: %d to %d", videoTime, cts));
+					}
+					videoTime = cts;
+				}
 			}
 			eventTime = videoTime;
 		} else if (rtmpEvent instanceof Invoke) {
 			if (rtmpEvent.getHeader().isTimerRelative()) {
+				if (dataTime < 0)
+					log.warn("First data [Invoke] timestamp is relative!"+rtmpEvent.getTimestamp());
 				dataTime += rtmpEvent.getTimestamp();
 			} else {
 				dataTime = rtmpEvent.getTimestamp();
 			}
 			return;
 		} else if (rtmpEvent instanceof Notify) {
 			if (rtmpEvent.getHeader().isTimerRelative()) {
+				if (dataTime == 0)
+					log.warn("First data [Notify] timestamp is relative!"+rtmpEvent.getTimestamp());
 				dataTime += rtmpEvent.getTimestamp();
 			} else {
 				dataTime = rtmpEvent.getTimestamp();
 			}
 			eventTime = dataTime;
 		}
@@ -321,13 +377,14 @@
 		// Notify event listeners
 		checkSendNotifications(event);
 
 		// Create new RTMP message, initialize it and push through pipe
 		RTMPMessage msg = new RTMPMessage();
 		msg.setBody(rtmpEvent);
-		msg.getBody().setTimestamp(eventTime);
+		msg.getBody().setTimestamp(eventTime); // rtmpEvent.setTimestamp(eventTime); ~ABSOLUTE!
+		// note this timestamp is set in event/body but not in the associated header.
 		try {
 			if (livePipe != null) {
 				livePipe.pushMessage(msg);
 			}
 			recordPipe.pushMessage(msg);
 		} catch (IOException err) {
@@ -747,13 +804,14 @@
 			videoCodecFactory = (VideoCodecFactory) getScope().getContext()
 					.getBean(VideoCodecFactory.KEY);
 			checkVideoCodec = true;
 		} catch (Exception err) {
 			log.warn("No video codec factory available.", err);
 		}
-		firstPacketTime = audioTime = videoTime = dataTime = -1;
+		firstPacketTime = -1;
+		audioTime = videoTime = dataTime = 0;
 		connMsgOut = consumerManager.getConsumerOutput(this);
 		connMsgOut.subscribe(this, null);
 		recordPipe = new InMemoryPushPushPipe();
 		Map<Object, Object> recordParamMap = new HashMap<Object, Object>();
 		// Clear record flag
 		recordParamMap.put("record", null);
