/// <summary> /// Construct. /// </summary> /// <param name="msMinDequeuePeriod">Minimum delay in milliseconds between dequeue events</param> /// <param name="scriptBitrate">Bits per second available in the current Windows Media script stream</param> public ScriptQueue(int msMinDequeuePeriod, int scriptBitrate, String baseUrl, String extent) { seenStudentSubmissions = new Hashtable(); rtUpdate = new RTUpdate(); mainQueue = new ArrayList(); subQueue = new ArrayList(); lastSlideTime = lastURLTime = lastScrollTime = DateTime.Now; lastEnqueueTime = DateTime.MinValue; lastSlideIndex = 0; lastDeckGuid = Guid.Empty; minDequeuePeriod = msMinDequeuePeriod; this.scriptBitrate = scriptBitrate; maxBytesPerSecond = scriptBitrate / 10; //theoretically we would divide by 8, but in practice we need a little extra safety factor. bwUpdateLockObject = new object(); this.baseUrl = baseUrl; this.extent = extent; lastUrl = null; scEnqueueMutex = new Mutex(false, "scEnqueue"); DequeueThread = new Thread(new ThreadStart(dequeueThread)); DequeueThread.Name = "ScriptQueue dequeue thread"; DequeueThread.SetApartmentState(ApartmentState.MTA); //Needed for WM interop DequeueThread.Start(); LastRtUpdate = new RTUpdate(); cp3Mgr = new CP3Manager.CP3Manager(); }
/// <summary> /// We receive this message when an instructor navigates between slides in a deck. There's also one that we /// want to ignore that occurs when the instructor opens a new deck but before navigating to a slide in the deck. /// </summary> /// <param name="stdm"></param> /// <returns></returns> internal object AddSlideDeckTraversal(CP3Msgs.SlideDeckTraversalMessage stdm) { //If m.Predecessor.Child is a TOC entry, look up info for RTUPdate there. if ((stdm.Predecessor != null) && (stdm.Predecessor.Child != null) && (stdm.Predecessor is CP3Msgs.SlideInformationMessage) && (stdm.Predecessor.Child is CP3Msgs.TableOfContentsEntryMessage) && (stdm.Parent != null) && (stdm.Parent is CP3Msgs.DeckInformationMessage)) { //Debug.WriteLine(stdm.Parent.Parent.ToString(), "*****"); //We get one of these when a deck is first opened, but before the instructor navigates to it. Filter it out //by remembering the current deck ID and ignoring messages for decks other than the current. if ((currentDeckId == Guid.Empty) || (currentDeckId.Equals((Guid)stdm.Parent.TargetId))) { currentSlideId = (Guid)stdm.Predecessor.TargetId; currentDeckId = (Guid)stdm.Parent.TargetId; RTUpdate rtu = this.toc.GetRtUpdate((Guid)stdm.Predecessor.Child.TargetId); return(rtu); } } else { //No warning because this happens when a new SS deck is created upon receipt of the first submission. Debug.WriteLine("A SlideDeckTraversal is being ignored because it lacks the expected message graph."); } return(null); }
internal RTUpdate GetRtUpdate(Guid tocKey) { if (this.toc.ContainsKey(tocKey)) { TocEntry entry = (TocEntry)toc[tocKey]; if (entry.DeckType == DeckTypeEnum.QuickPoll) { Debug.WriteLine(""); } if (!UpdateAssociation(entry)) { //If we failed to map a student submission to a deck it could be because the SS is on // a whiteboard page, or it could be that we don't yet have a TOC entry for the // original slide. For now, just treat both cases as if // they were whiteboard. // P2: Make a way to mark the message as SS and leave the deck association empty, and // have that be understood as a SS on a WB. I think this requires an update to WebViewer? RTUpdate rtu = entry.ToRtUpdate(); rtu.DeckType = (int)DeckTypeEnum.Whiteboard; return(rtu); } return(entry.ToRtUpdate()); } return(null); }
private void UpdateRTUpdate(RTUpdate rtu) { lock (rtUpdate) { rtUpdate = rtu; rtUpdate.BaseUrl = baseUrl; rtUpdate.Extent = extent; } }
private void enqueueMain(WorkItem wi, RTUpdate rtu) { lock (mainQueue) mainQueue.Add(wi); if (wi.Type != "URL") { if (OnEnqueue != null) { ScriptEventArgs dea = new ScriptEventArgs(Convert.ToBase64String(wi.BC.Buffer, wi.BC.Index, wi.BC.Length), wi.Type); OnEnqueue(this, dea); } } }
/// <summary> /// Fetch image from web for Presenter 2 data /// </summary> /// <param name="o"></param> private void GetWebImageThread2(object o) { RTUpdate rtu = (RTUpdate)o; if ((rtu.BaseUrl == null) || (rtu.Extent == null)) { return; } if (rtu.DeckGuid == Guid.Empty) { return; } System.Drawing.Image tmpImage; string imgURL; Guid pDeckGuid = rtu.DeckGuid; int pSlideNumber = rtu.SlideIndex + 1; /// Student submissions need to map to a presentation deck and slide. if ((rtu.DeckType == (Int32)DeckTypeEnum.StudentSubmission) || (rtu.DeckType == (Int32)DeckTypeEnum.QuickPoll)) { pDeckGuid = rtu.DeckAssociation; pSlideNumber = rtu.SlideAssociation + 1; } //implicit assumption that rtu.BaseUrl includes the trailing '/'. imgURL = rtu.BaseUrl + pDeckGuid.ToString() + "/slide" + pSlideNumber.ToString() + "." + rtu.Extent; try { tmpImage = System.Drawing.Image.FromStream(webClient.OpenRead(imgURL)); } catch { parent.LoggerWriteInvoke("Exception while opening image url: " + imgURL); return; } //put the new image in the SlideDeck. int internalIndex = slideMap.GetMapping(rtu.SlideIndex, rtu.DeckGuid); slideDeck.SetSlide(internalIndex, new Slide(new Bitmap(tmpImage), "Slide " + internalIndex.ToString())); // display the newly loaded slide. DisplaySlideIndex(internalIndex); // the latter two params are not relevant here. }
internal RTUpdate ToRtUpdate() { RTUpdate rtu = new RTUpdate(); rtu.DeckGuid = deckId; rtu.SlideIndex = slideIndex; rtu.DeckType = (int)deckType; rtu.SlideSize = slideSize; //background colors can be attached to any slide and deck, but they only show //on whiteboards, or on the area around a minimized slide. if (!backgroundColor.Equals(Color.Empty)) { //Explicitly set slide color has top priority rtu.BackgroundColor = backgroundColor; } else if ((TableOfContents.TheInstance.DeckBackgroundColors.ContainsKey(deckId)) && (!TableOfContents.TheInstance.DeckBackgroundColors[deckId].Equals(Color.Empty))) { //DeckBackground color is checked next. rtu.BackgroundColor = TableOfContents.TheInstance.DeckBackgroundColors[deckId]; } else { //If none are set, default to white. rtu.BackgroundColor = Color.White; } if ((deckType == DeckTypeEnum.StudentSubmission) || (deckType == DeckTypeEnum.QuickPoll)) { rtu.DeckAssociation = deckAssociation; rtu.SlideAssociation = slideAssociation; rtu.DeckTypeAssociation = (int)deckTypeAssociation; } return(rtu); }
public static void CopyRtUpdate(RTUpdate from, ref RTUpdate to) { if (from == null) { return; } if (to == null) { return; } to.BackgroundColor = from.BackgroundColor; to.BaseUrl = from.BaseUrl; to.DeckAssociation = from.DeckAssociation; to.DeckGuid = from.DeckGuid; to.DeckType = from.DeckType; to.Extent = from.Extent; to.ScrollExtent = from.ScrollExtent; to.ScrollPosition = from.ScrollPosition; to.SlideAssociation = from.SlideAssociation; to.SlideIndex = from.SlideIndex; to.SlideSize = from.SlideSize; }
public static bool RtUpdatesEqual(RTUpdate rtu1, RTUpdate rtu2) { if ((rtu1 == null) || (rtu2 == null)) { return(false); } if ((rtu1.BackgroundColor != rtu2.BackgroundColor) || (rtu1.BaseUrl != rtu2.BaseUrl) || (rtu1.DeckAssociation != rtu2.DeckAssociation) || (rtu1.DeckGuid != rtu2.DeckGuid) || (rtu1.DeckType != rtu2.DeckType) || (rtu1.Extent != rtu2.Extent) || (rtu1.ScrollExtent != rtu2.ScrollExtent) || (rtu1.ScrollPosition != rtu2.ScrollPosition) || (rtu1.SlideAssociation != rtu2.SlideAssociation) || (rtu1.SlideIndex != rtu2.SlideIndex) || (rtu1.SlideSize != rtu2.SlideSize)) { return(false); } return(true); }
/// <summary> /// This is a message used in the beacon, and it is also the message that indicates when /// an instructor navigates to an initial slide in a newly opened deck. /// </summary> /// <param name="icdtcm"></param> /// <returns></returns> /// This is the beacon message graph we expect: /// {InstructorMessage; /// child={InstructorCurrentDeckTraversalChangedMessage; /// pred={SlideInformationMessage; /// pred={InstructorCurrentPresentationChangedMessage;}; /// child={TableOfContentsEntryMessage;};};};} internal List <object> AddInstructorCurrentDeckTraversalChanged(UW.ClassroomPresenter.Network.Messages.Network.InstructorCurrentDeckTraversalChangedMessage icdtcm) { //If m.Predecessor.Child is a TOC entry, return a RTUPdate. List <object> outputMessages = null; if ((icdtcm.Predecessor != null) && (icdtcm.Predecessor.Child != null) && (icdtcm.Predecessor is CP3Msgs.SlideInformationMessage) && (icdtcm.Predecessor.Child is CP3Msgs.TableOfContentsEntryMessage)) { if (icdtcm.Predecessor.Predecessor == null) //This effectively filters out the beacon messages. //In some late-joiner scenarios, we may not have the TOC entry. { if (!toc.ContainsEntry((Guid)icdtcm.Predecessor.Child.TargetId)) { string err; Guid slideId = toc.AddTableOfContentsEntry((CP3Msgs.TableOfContentsEntryMessage)icdtcm.Predecessor.Child, icdtcm.DeckId, icdtcm.Dispositon, out err); if (err != null) { Debug.WriteLine(err); } else if (!slideId.Equals(Guid.Empty)) { outputMessages = this.getCachedStrokes(slideId); } } if (currentDeckId.Equals(icdtcm.DeckId) && currentSlideId.Equals((Guid)icdtcm.Predecessor.TargetId)) { Debug.WriteLine("***Ignoring InstructorCurrentDeckTraversalChanged because it matches the current slide and deck"); return(outputMessages); } RTUpdate rtu = this.toc.GetRtUpdate((Guid)icdtcm.Predecessor.Child.TargetId); if (rtu != null) { currentDeckId = icdtcm.DeckId; currentSlideId = (Guid)icdtcm.Predecessor.TargetId; } else { Debug.WriteLine("Warning: Navigation failure."); } if (outputMessages == null) { outputMessages = new List <object>(); } outputMessages.Add(rtu); return(outputMessages); } else { //The beacon also causes navigation in some cases, eg. the initial slide after we join. //If the beacon has a toc entry we don't already have, add it. if (!toc.ContainsEntry((Guid)icdtcm.Predecessor.Child.TargetId)) { string err; Guid slideId = toc.AddTableOfContentsEntry((CP3Msgs.TableOfContentsEntryMessage)icdtcm.Predecessor.Child, icdtcm.DeckId, icdtcm.Dispositon, out err); if (err != null) { Debug.WriteLine(err); } else if (!slideId.Equals(Guid.Empty)) { outputMessages = this.getCachedStrokes(slideId); } } //if the beacon indicates a slide other than the current slide, navigate there. if ((!currentSlideId.Equals((Guid)icdtcm.Predecessor.TargetId)) || (!currentDeckId.Equals(icdtcm.DeckId))) { currentSlideId = (Guid)icdtcm.Predecessor.TargetId; RTUpdate rtu = this.toc.GetRtUpdate((Guid)icdtcm.Predecessor.Child.TargetId); currentDeckId = (Guid)icdtcm.DeckId; currentSlideId = (Guid)icdtcm.Predecessor.TargetId; if (outputMessages == null) { outputMessages = new List <object>(); } outputMessages.Add(rtu); return(outputMessages); } } } else { warning += "Warning: Found InstructorCurrentDeckTraversalChangedMessage message without a TOC Entry. "; } return(outputMessages); }
/// <summary> /// Filter the contents of the existing tempdir using the specified /// difference threshold. Rewrite tempdir, metadata and differenceMetrics. /// </summary> /// <param name="threshold"></param> internal void Refilter(double threshold) { //Make an imageFilter instance and configure with the new threshold. ImageFilter.ImageFilter imgFilter = null; try { imgFilter = new ImageFilter.ImageFilter(); imgFilter.DifferenceThreshold = threshold; } catch { return; } Console.WriteLine("Refiltering framegrabs using threshold: " + threshold.ToString()); List <double> newDifferenceMetrics = new List <double>(); List <PresentationMgr.DataItem> newMetadata = new List <PresentationMgr.DataItem>(); RTUpdate rtu = new RTUpdate(); this.deckGuid = Guid.NewGuid(); rtu.DeckGuid = this.deckGuid; rtu.SlideSize = 1.0; rtu.DeckType = (Int32)DeckTypeEnum.Presentation; string filebase = "slide"; string extent = ".jpg"; int fileindex = 1; try { string newDir = Utility.GetTempDir(); Directory.CreateDirectory(newDir); string[] files = Directory.GetFiles(this.tempdir, "*.jpg"); Array.Sort(files, new PMPComparer()); string previous = null; for (int i = 0; i < files.Length; i++) { string file = files[i]; string msg; bool error = false; double metric; if (imgFilter.ImagesDiffer(previous, file, out msg, out error, out metric)) { string newFilename = filebase + fileindex.ToString() + extent; File.Copy(file, Path.Combine(newDir, newFilename)); rtu.SlideIndex = fileindex - 1; // This is a zero-based index newMetadata.Add(new PresentationMgr.DataItem(metadata[i].Timestamp, PresentationMgr.CopyRTUpdate(rtu))); newDifferenceMetrics.Add(metric); DateTime t = new DateTime(metadata[i].Timestamp); Console.WriteLine("Refilter keeping old index: " + (i + 1).ToString() + "; new index: " + fileindex.ToString() + "; difference: " + metric.ToString() + "; time: " + t.ToString()); fileindex++; previous = file; } } this.metadata = newMetadata; this.differenceMetrics = newDifferenceMetrics; Directory.Delete(this.tempdir, true); this.tempdir = newDir; } catch (Exception) { return; } }
/// <summary> /// Capture stills from the video stream. Create a temporary directory and save the images there. /// Compile a list of DataItem objects to indicate when the slide transitions should take place. /// </summary> /// <returns></returns> public string Process() { ImageFilter.ImageFilter imgFilter = null; try { imgFilter = new ImageFilter.ImageFilter(); imgFilter.DifferenceThreshold = this.differenceThreshold; } catch { this.errorLog.Append("Video capture images in the presentation will not be filtered probably " + "because ImageMagick is not available in the configuration.\r\n"); } this.differenceMetrics = new List <double>(); metadata = new List <PresentationMgr.DataItem>(); RTUpdate rtu = new RTUpdate(); this.deckGuid = Guid.NewGuid(); rtu.DeckGuid = this.deckGuid; rtu.SlideSize = 1.0; rtu.DeckType = (Int32)DeckTypeEnum.Presentation; this.videoStream = new StreamMgr(videoDescriptor.VideoCname, videoDescriptor.VideoName, new DateTime(this.start), new DateTime(this.end), false, PayloadType.dynamicVideo); this.videoStream.ToRawWMFile(this.progressTracker); MediaTypeVideoInfo mtvi = videoStream.GetUncompressedVideoMediaType(); this.tempdir = Utility.GetTempDir(); Directory.CreateDirectory(this.tempdir); string filebase = "slide"; string extent = ".jpg"; int fileindex = 1; BufferChunk bc; long time; bool newStream; string previousFile = null; this.stopNow = false; while ((!stopNow) && (videoStream.GetNextSample(out bc, out time, out newStream))) { if ((time - lastFramegrab) >= (long)(this.frameGrabIntervalMs * Constants.TicksPerMs)) { DateTime dt = new DateTime(time); Debug.WriteLine("time=" + dt.ToString() + ";length=" + bc.Length.ToString()); lastFramegrab = time; string filepath = Path.Combine(tempdir, filebase + fileindex.ToString() + extent); PixelFormat pixelFormat = subtypeToPixelFormat(mtvi.SubType); Bitmap bm = new Bitmap(mtvi.VideoInfo.BitmapInfo.Width, mtvi.VideoInfo.BitmapInfo.Height, pixelFormat); BitmapData bd = bm.LockBits(new Rectangle(0, 0, mtvi.VideoInfo.BitmapInfo.Width, mtvi.VideoInfo.BitmapInfo.Height), ImageLockMode.ReadWrite, pixelFormat); Marshal.Copy(bc.Buffer, 0, bd.Scan0, bc.Length); bm.UnlockBits(bd); bm.RotateFlip(RotateFlipType.RotateNoneFlipY); if ((SCALE) && (mtvi.VideoInfo.BitmapInfo.Width >= 1280)) { int w = mtvi.VideoInfo.BitmapInfo.Width / 2; int h = mtvi.VideoInfo.BitmapInfo.Height / 2; Bitmap scaled = new Bitmap(bm, new Size(w, h)); scaled.Save(filepath, ImageFormat.Jpeg); } else { bm.Save(filepath, ImageFormat.Jpeg); } if (imgFilter != null) { string filterMsg; bool filterError; double metric; bool differ = imgFilter.ImagesDiffer(filepath, previousFile, out filterMsg, out filterError, out metric); if (filterError) { //this.errorLog.Append(filterMsg); Console.WriteLine(filterMsg); } if (!differ) { continue; } this.differenceMetrics.Add(metric); Console.WriteLine("Framegrab slide index: " + fileindex.ToString() + "; difference: " + metric.ToString() + "; time: " + dt.ToString()); } rtu.SlideIndex = fileindex - 1; // This is a zero-based index metadata.Add(new PresentationMgr.DataItem(time - this.offset, PresentationMgr.CopyRTUpdate(rtu))); fileindex++; previousFile = filepath; } } return(null); }