Advertisement
Guest User

Untitled

a guest
Dec 5th, 2019
161
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <script type="text/javascript">
  2. var alwaysShowControls = false;
  3. var statusText = document.getElementById('statusText');
  4. var hideStatus = document.getElementById('hideStatus');
  5. var statusBox = document.getElementById('statusBox');
  6. var modelStatus = document.getElementById('model_status');
  7. var modelDetail = document.getElementById('model_detail');
  8. var detailText = document.getElementById('detailText');
  9. var thumbPreviewText = document.getElementById('thumbPreviewText');
  10. var snapImg = document.getElementById('snapImg');
  11. var liveIcon = document.getElementById('liveIcon');
  12. var overlayFrame = document.getElementById('overlayFrame');
  13. var previewFrame = document.getElementById('previewFrame');
  14. var nextFeed = document.getElementById('nextFeed');
  15. var prevFeed = document.getElementById('prevFeed');
  16. var attachedInterval = 0;
  17. var unattachedInterval = 0;
  18.  
  19. var g_hSelf = { uid: 0, sid: 0 };
  20. var g_nState = FCS.FCVIDEO_RX_IDLE;
  21. var obsFeeds = { };
  22. var attachedFeed = new NgxStreamId({"room":0,"serv":0,"phase":"z"});
  23. var selectedKeyId = new NgxStreamId({"room":0,"serv":0,"phase":"z"});
  24. var liveIconTimer = 0;
  25. var clubGroupId = 0;
  26.  
  27. function onClubShowStart(sArg)
  28. {
  29.     var o = ParseJSON(sArg);
  30.     console.log('ClubShowStart event called: ' + o);
  31. }
  32.  
  33. function stateBroadcasting(nState)
  34. {
  35.     return (nState === FCS.FCVIDEO_TX_IDLE || nState === FCS.FCVIDEO_TX_CLUB || nState === FCS.FCVIDEO_TX_GRP || nState === FCS.FCVIDEO_TX_PVT);
  36. }
  37.  
  38. function onSessionState(sData)
  39. {
  40.     var hData = null;
  41.     if (typeof(sData) === 'string')
  42.     {
  43.         hData = ParseJSON(sData);
  44.         if (hData && hData.hasOwnProperty("vs"))
  45.         {
  46.             var newState = parseInt(hData.vs);
  47.             var wasBroadcasting = stateBroadcasting( g_nState );
  48.             var isBroadcasting = stateBroadcasting( newState );
  49.             if (wasBroadcasting && !isBroadcasting)
  50.             {
  51.                 if (!attachedFeed.isNull())
  52.                 {
  53.                     // if we have no feeds, clear attachement (error), and if we have more
  54.                     // than 1 feed, clear attachment so model will know to choose the right
  55.                     // feed before returning to a broadcast state
  56.                     var numFeeds = Object.size(obsFeeds);
  57.                     if (numFeeds < 1 || numFeeds > 1)
  58.                     {
  59.                         unattachObsFeed(attachedFeed, "onSessionState: broadcast ending, unattaching 1 of " + numFeeds + " feeds");
  60.                         attachedFeed.clear();
  61.                         mfc.onChangeAttachment();
  62.                     }
  63.                     else Log('AMDBG: onSessionState(): broadcast ending, but attachedFeed remains since numFeeds is 1');
  64.                 }
  65.                 else Log('AMDBG: onSessionState(): BUG: broadcast ending, but no attached feed (?)');
  66.             }
  67.             else if (newState != g_nState)
  68.                 Log('AMDBG: onSessionState(): state change not broadcast ending for ' + g_nState + ' => ' + newState);
  69.  
  70.             renderSessionState(newState);
  71.         }
  72.     }
  73.     else Log("onSessionState() unable to decode or parse json data: " + JSON.stringify(sData));
  74. }
  75.  
  76. function onSetClubGroup(sArg)
  77. {
  78.     var o = ParseJSON(sArg);
  79.     if (o && o.hasOwnProperty("gid"))
  80.     {
  81.         clubGroupId = parseInt(o.gid);
  82.     }
  83.     else clubGroupId = 0;
  84.  
  85.     renderSessionState(g_nState);
  86. }
  87.  
  88. function parseNgxStream(sid)
  89. {
  90.     var hObj = null;
  91.     if (typeof sid === 'string')
  92.     {
  93.         try { hObj = JSON.parse(sid); }
  94.         catch (e) { Log('error parsing string for NgxStreamId: ' + sid); }
  95.     }
  96.     else if (isObject(sid))
  97.     {
  98.         hObj = sid;
  99.     }
  100.  
  101.     if (hObj && hObj.hasOwnProperty('room'))
  102.     {
  103.         sid = new NgxStreamId( hObj );
  104.     }
  105.     else Log('error parsing object for NgxStreamId: ' + JSON.stringify(sid));
  106.  
  107.     return sid;
  108. }
  109.  
  110. hideStatus.onclick = function()
  111. {
  112.     // Short click action: toggle control overlay (forced on or auto-on/off)
  113.     alwaysShowControls = !alwaysShowControls;
  114.     redrawControls();
  115. };
  116.  
  117. liveIcon.onmousedown = function()
  118. {
  119.     if (liveIconTimer > 0)
  120.         clearTimeout(liveIconTimer);
  121.  
  122.     liveIconTimer = setTimeout(function()
  123.     {
  124.         liveIconTimer = 0;
  125.         if (!attachedFeed.isNull())
  126.         {
  127.             if (selectedKeyId.isEqual(attachedFeed))
  128.             {
  129.                 if (obsFeeds.hasOwnProperty( attachedFeed.toString() )     &&
  130.                     obsFeeds[ attachedFeed.toString() ].mact == g_hSelf.sid)
  131.                 {
  132.                     manually_unattachObsFeed(attachedFeed);
  133.                 }
  134.                 else Log('UNATTACH ERR cant unattach[' + attachedFeed.toString() + '], obsFeeds[].mact not us or obsFeeds missing attachedFeed');
  135.             }
  136.             else Log('UNATTACH ERR cant unattach[' + selectedKeyId.toString() + '], its not your attached[' + attachedFeed.toString() + ']: unattach that one first');
  137.         }
  138.         else if (!selectedKeyId.isNull())
  139.         {
  140.             if (obsFeeds.hasOwnProperty( selectedKeyId.toString() ))
  141.             {
  142.                 if (obsFeeds[ selectedKeyId.toString() ].mact == 0)
  143.                 {
  144.                     attachObsFeed(selectedKeyId);
  145.                 }
  146.                 else Log('ATTACH ERR cant attach this, mact has it owned by sid[' + obsFeeds[ selectedKeyId.toString() ].mact + '], self[' + g_hSelf.sid + ']');
  147.             }
  148.             else Log('selectedKeyId of ' + selectedKeyId.toString() + ' not found in obsFeeds[] !!');
  149.         }
  150.        
  151.     }, 1500);
  152. };
  153.  
  154. liveIcon.onmouseup = function()
  155. {
  156.     // if short click, liveIconTimer will be there still, so cancel and
  157.     // execute short click action, otherwise longclick arleady happened
  158.     // and we dont need to do anything
  159.     //
  160.     if (liveIconTimer > 0)
  161.     {
  162.         clearTimeout(liveIconTimer);
  163.         liveIconTimer = 0;
  164.        
  165.         // Short click action: toggle control overlay (forced on or auto-on/off)
  166.         alwaysShowControls = !alwaysShowControls;
  167.         redrawControls();
  168.     }
  169. };
  170.  
  171. function SetName(sData)
  172. {
  173.     var hSelf = ParseJSON(sData);
  174.     if (hSelf)
  175.     {
  176.         g_hSelf = hSelf;
  177.         g_hSelf.channel = UserChannel(g_hSelf.uid);
  178.     }
  179.     else Log('Failed to g_hSelf for sData:' + sData);
  180. }
  181.  
  182. function previewThumb(sid)
  183. {
  184.     sid = parseNgxStream(sid);
  185.     if (sid instanceof NgxStreamId)
  186.     {
  187.         sKey = sid.toString();
  188.         if (obsFeeds.hasOwnProperty(sKey))
  189.         {
  190.             previewFrame.style.backgroundImage = "url('" + obsFeeds[sKey]['t_url'] + "')";
  191.  
  192.             previewFrame.innerHTML = "<span id=previewText class=previewText>Click to Close</span>";
  193.             previewFrame.onclick = function() { hideObj(previewFrame); };
  194.             showObj(previewFrame);
  195.             clearTimeout(fadePreviewTimer);
  196.             fadePreviewTimer = setTimeout( function() { fadeOut(document.getElementById('previewText')); }, 500);
  197.         }
  198.         else Log('previewThumb() cannot find ' + sKey + ' in obsFeeds[]');
  199.     }
  200.     else Log('previewThumb() expected NgxStreamId obj, dropping: ' + JSON.stringify(sid));
  201. }
  202.  
  203. function attachObsFeed(sid)
  204. {
  205.     sid = parseNgxStream(sid);
  206.     if (sid instanceof NgxStreamId)
  207.     {
  208.         var sKey = sid.toString();
  209.  
  210.         if (obsFeeds.hasOwnProperty(sKey))
  211.         {
  212.             // if no obs feeds at all are attached to model, then this method
  213.             // will take the obs feed keyId and attach it to the model.  This will
  214.             // make the model's camserv become the camserv of this obs feed, and set
  215.             // her state to tx_idle or tx_away (tx_idle by default, tx_away only if away
  216.             // mode is currently set)
  217.             //
  218.             obsFeeds[sKey].mact = g_hSelf.sid;
  219.                
  220.             attachedFeed.load(sid);
  221.             mfc.AttachObsFeed( JSON.stringify({"sid":sid.toJson(), "updateServer": true }) );
  222.             mfc.onChangeAttachment();
  223.  
  224.             selectFeed(sid);
  225.         }
  226.         else Log('Cannot attach to feed ' + sKey + ', not found in obsFeeds[]');
  227.     }
  228.     else Log('Cannot attach, expected NgxStreamId for sid, not: ' + JSON.stringify(sid));
  229. }
  230.  
  231. function manually_unattachObsFeed(sid)
  232. {
  233.     obsOpts.disableAutoAttach = true;
  234.     unattachObsFeed(sid, "manually_unattachObsFeed: clicked unattach link");
  235. }
  236.  
  237. function unattachObsFeed(sid, sLog)
  238. {
  239.     sid = parseNgxStream(sid);
  240.     if (sid instanceof NgxStreamId)
  241.     {
  242.         var sKey = sid.toString();
  243.  
  244.         // if obs feed keyId is attached to this model, this method will disconnect
  245.         // that OBS feed from being attached to model and causes model's camserv to
  246.         // become 0, and state to go to rx_idle.
  247.         //
  248.         var logCtx = {
  249.             "stack": getStackTrace(),
  250.             "sid": sid.toJson(),
  251.             "attachedFeed": attachedFeed.toJson(),
  252.             "log": sLog
  253.         };
  254.         Log( JSON.stringify(logCtx) );
  255.         mfc.UnattachObsFeed( JSON.stringify({"sid":sid.toJson(), "obslog":logCtx, "updateServer": true }) );
  256.  
  257.         if (obsFeeds.hasOwnProperty(sKey))
  258.         {
  259.             if (attachedFeed.isEqual (obsFeeds[sKey].sid ))
  260.             {
  261.                 obsFeeds[sKey].mact = 0;
  262.                 attachedFeed.clear();
  263.                 selectFeed(sid);
  264.                 mfc.onChangeAttachment();
  265.             }
  266.         }
  267.         else Log('Cannot unattach to feed ' + sKey + ', not found in obsFeeds[]');
  268.        
  269.     }
  270.     else Log('Cannot unattach, expected NgxStreamId for sid, not: ' + JSON.stringify(sid));
  271. }
  272.  
  273. // fade out
  274. function fadeOut(el){
  275.   el.style.opacity = 1;
  276.  
  277.   (function fade() {
  278.     if ((el.style.opacity -= .01) < 0) {
  279.       el.style.display = "none";
  280.     } else {
  281.       queuedFrames.push( requestAnimationFrame(fade) );
  282.     }
  283.   })();
  284. }
  285. // fade in
  286.  
  287. // semi-fade out
  288. function semiFadeOut(el){
  289.   el.style.opacity = 1;
  290.  
  291.   (function impl_semiFadeOut() {
  292.     el.style.opacity -= 0.05;
  293.     if (el.style.opacity > 0.06)
  294.         semiFadeOut.queuedFrames.push( requestAnimationFrame(impl_semiFadeOut) );
  295.   })();
  296. }
  297. semiFadeOut.queuedFrames = Array();
  298. semiFadeOut.clearQueuedFrames = function()
  299. {
  300.     if (semiFadeOut.queuedFrames.length > 0)
  301.     {
  302.         for (var n = 0; n < semiFadeOut.queuedFrames.length; n++)
  303.             window.cancelAnimationFrame(semiFadeOut.queuedFrames[n]);
  304.         semiFadeOut.queuedFrames = Array();
  305.     }
  306. };
  307.  
  308. // semi-fade in
  309. function semiFadeIn(el, display){
  310.   el.style.opacity = .15;
  311.   //el.style.display = display || "block";
  312.   (function impl_semiFadeIn() {
  313.     var val = parseFloat(el.style.opacity);
  314.     if (!((val += .05) >= 1)) {
  315.       el.style.opacity = val;
  316.         semiFadeIn.queuedFrames.push( requestAnimationFrame(impl_semiFadeIn) );
  317.     }
  318.   })();
  319. }
  320. semiFadeIn.queuedFrames = Array();
  321. semiFadeIn.clearQueuedFrames = function()
  322. {
  323.     if (semiFadeIn.queuedFrames.length > 0)
  324.     {
  325.         for (var n = 0; n < semiFadeIn.queuedFrames.length; n++)
  326.             window.cancelAnimationFrame(semiFadeIn.queuedFrames[n]);
  327.         semiFadeIn.queuedFrames = Array();
  328.     }
  329. };
  330.  
  331. var obsOpts = {
  332.     uid:                        0,
  333.     obs_publish_key:            '',
  334.     obs_publish_phase:          'z',
  335.     obs_autoattach:             1,
  336.     obs_live_preview:           450,
  337.     ngx_autohide_preview:       1,
  338.    
  339.     // manually unattaching a stream flips this to true, and
  340.     // auto-attaches stop until obs html is reloaded.
  341.     //
  342.     //disableAutoAttach:          false,
  343.     disableAutoAttach:          true,          // HACKHACK
  344. };
  345.  
  346. var obsCtx = {
  347.     //
  348.     // context property tracking the fade of a live snap from 1.0 to 0.0
  349.     // opacity when live broadcast starts, if ngx_autohide_preview is 1
  350.     // if ngx_autohide_preview is 1
  351.     //
  352.     previewFadeOut:             1.0,
  353. };
  354.  
  355. function setObsVars(sData)
  356. {
  357.     var hObj = ParseJSON( sData );
  358.     if (hObj)
  359.     {
  360.         // Setup object containing just default key/value pairs
  361.         //var _defaults = { disableAutoAttach: false };
  362.         var _defaults = { disableAutoAttach: true };        // HACKHACK
  363.  
  364.         // for any of the default pairs that are missing from thew new object provided, copy in
  365.         // either the existing obsOpts valuye for it (if that exists), or our default value
  366.         for (sK in _defaults)
  367.             if (!hObj.hasOwnProperty(sK))
  368.                 hObj[sK] = (obsOpts.hasOwnProperty(sK) ? obsOpts[sK] : _defaults[sK]);
  369.        
  370.         // now override obsOpts with the hObj we were given
  371.         obsOpts = hObj;
  372.  
  373.         // manually copy out attachedFeed id
  374.         if (hObj.hasOwnProperty("_attached_sid"))
  375.         {
  376.             attachedFeed.load( hObj._attached_sid );
  377.             mfc.onChangeAttachment();
  378.         }
  379.     }
  380. }
  381.  
  382. function getStreamType(sid)
  383. {
  384.     var sStreamType = "Unknown";
  385.     if (sid.room >= 200000000 && sid.room < 300000000)
  386.     {
  387.         if      (sid.phase == 'c')  sStreamType = "Club";
  388.         else if (sid.phase == 'p')  sStreamType = "Private";
  389.         else if (sid.phase == 'g')  sStreamType = "Group";
  390.         else                        sStreamType = "InvalidPhase";
  391.     }
  392.     else if (sid.room >= 100000000 && sid.room < 200000000)
  393.     {
  394.         sStreamType = "Public";
  395.     }
  396.     else sStreamType = "InvalidRoom";
  397.  
  398.     return sStreamType;
  399. }
  400.  
  401. function renderSessionState(newState)
  402. {
  403.     var nFeedSz = Object.size(obsFeeds);
  404.     var oldState = g_nState;
  405.     g_nState = newState;
  406.     var smallFonts = false;
  407.    
  408.     if      (g_nState == FCS.FCVIDEO_TX_IDLE)   sState = "Live";
  409.     else if (g_nState == FCS.FCVIDEO_TX_AWAY)   sState = "Away";
  410.     else if (g_nState == FCS.FCVIDEO_TX_PVT)    sState = "PrivateChat"
  411.     else if (g_nState == FCS.FCVIDEO_TX_GRP)    sState = "GroupChat";
  412.     else if (g_nState == FCS.FCVIDEO_TX_CLUB)   sState = "Clubshow";
  413.     else if (g_nState == FCS.FCVIDEO_OFFLINE)   sState = "Offline";
  414.     else if (g_nState == FCS.FCVIDEO_RX_IDLE)
  415.     {
  416.         if      (nFeedSz == 0)                  sState = "Waiting for OBS to start streaming...";
  417.         else if (attachedFeed.isNull())         sState = "Waiting for stream to be attached...";
  418.         else if (   attachedFeed.room   >= 200000000
  419.                  && attachedFeed.room   <  300000000
  420.                  && attachedFeed.phase  == 'c'
  421.                  && clubGroupId         == 0)
  422.         {
  423.                                                 sState = "Waiting for clubs to be chosen...";
  424.         }
  425.         else                                    sState = "Ready to start " + getStreamType(attachedFeed) + " show...";
  426.         smallFonts = true;
  427.     }
  428.     else                                        sState = "Unknown: " + g_nState;
  429.            
  430.     if (modelStatus)
  431.     {
  432.         dropStyle( modelStatus, smallFonts ? "largeStatus" : "smallStatus" );
  433.         addStyle(  modelStatus, smallFonts ? "smallStatus" : "largeStatus" );
  434.         modelStatus.innerHTML = sState;
  435.  
  436.         if (!alwaysShowControls)
  437.         {
  438.             //console.log('showObj() modelStatus from renderSessionState()');
  439.             showObj(modelStatus);
  440.         }
  441.     }
  442.  
  443.     // Reset intervals on major state changes
  444.     if (oldState != g_nState)
  445.     {
  446.         unattachedInterval = 0;
  447.         attachedInterval = 0;
  448.         obsCtx.previewFadeOut = 1.0;
  449.  
  450.         // changed to idle/club and attached to stream? start countdown for previews to disappear
  451.         if ((g_nState == FCS.FCVIDEO_TX_IDLE || g_nState == FCS.FCVIDEO_TX_CLUB) && !attachedFeed.isNull())
  452.         {
  453.             // start drawing text banner/download of thumbs queued
  454.             setStatusDetail();
  455.         }
  456.         else if ((oldState == FCS.FCVIDEO_TX_IDLE || oldState == FCS.FCVIDEO_TX_CLUB) && obsOpts.ngx_autohide_preview == 1)
  457.         {
  458.             // cancel/queue resume of preview snaps if we are leaving a broadcast state
  459.             // when autohide was active. this forces us to resume preview callback loop.
  460.             activeThumb_cancel();
  461.         }
  462.     }
  463. }
  464.        
  465.  
  466. function setStatusDetail()
  467. {
  468.     if (g_nState == FCS.FCVIDEO_TX_IDLE || g_nState == FCS.FCVIDEO_TX_CLUB)
  469.     {
  470.         var nSec = Math.round( (obsCtx.previewFadeOut * 3) + .5 );
  471.         if (nSec > 3) nSec = 3;
  472.  
  473.         if (obsOpts.ngx_autohide_preview == 1)
  474.         {
  475.             modelDetail.innerHTML = "Hiding preview in <b>" + nSec + "</b>";
  476.             showObj(modelDetail);
  477.  
  478.             obsCtx.previewFadeOut -= .080;
  479.        
  480.             // Fade below 100% opacity, but if 0.0, then replace with empty div
  481.             if (obsCtx.previewFadeOut > 0)
  482.             {
  483.                 if (activeThumbImg)
  484.                     activeThumbImg.style.opacity = obsCtx.previewFadeOut;
  485.                 if (lastThumbImg)
  486.                     lastThumbImg.style.opacity = obsCtx.previewFadeOut;
  487.                 setStatusDetail.hUpdate = setTimeout(function() { setStatusDetail(); }, 230);
  488.             }
  489.             else
  490.             {
  491.                 renderPreviewDiv('activeBroadcastSnap');
  492.                 obsCtx.previewFadeOut = 0;
  493.                 hideObj(modelDetail);
  494.             }
  495.         }
  496.  
  497.         return;
  498.     }
  499.     else
  500.     {
  501.         clearTimeout( setStatusDetail.hUpdate );
  502.         setStatusDetail.hUpdate = 0;
  503.         obsCtx.previewFadeOut = 1.0;
  504.         hideObj(modelDetail);
  505.     }
  506. }
  507. setStatusDetail.hUpdate = 0;
  508.  
  509. function loadNextFeed()
  510. {
  511.     if (obsFeeds.hasOwnProperty(selectedKeyId.toString()))
  512.     {
  513.         if (obsFeeds[selectedKeyId.toString()].nextId != null)
  514.         {
  515.             selectFeed(obsFeeds[selectedKeyId.toString()].nextId);
  516.         }
  517.         else Log('loadNextFeed(): prevId NULL for ' + JSON.stringify( obsFeeds[selectedKeyId.toString()] ) );
  518.     }
  519.     else Log('loadNextFeed(): selectedKeyId not in obsFeeds: ' + selectedKeyId.toString());
  520. }
  521.  
  522. function loadPrevFeed()
  523. {
  524.     if (obsFeeds.hasOwnProperty(selectedKeyId.toString()))
  525.     {
  526.         if (obsFeeds[selectedKeyId.toString()].prevId != null)
  527.         {
  528.             selectFeed(obsFeeds[selectedKeyId.toString()].prevId);
  529.         }
  530.         else Log('loadPrevFeed(): prevId NULL for ' + JSON.stringify( obsFeeds[selectedKeyId.toString()] ) );
  531.     }
  532.     else Log('loadPrevFeed(): selectedKeyId not in obsFeeds: ' + selectedKeyId.toString());
  533. }
  534.  
  535. function removeFeed(hFeed)
  536. {
  537.     var oldAttachedFeed = null;
  538.     if ( ! attachedFeed.isNull() )
  539.     {
  540.         oldAttachedFeed = new NgxStreamId({ "room": attachedFeed.room, "serv": attachedFeed.serv, "phase": attachedFeed.phase });
  541.     }
  542.     else oldAttachedFeed = new NgxStreamId({"room":0,"serv":0,"phase":"z"});
  543.  
  544.     hideObj( previewFrame );
  545.  
  546.     if (hFeed.hasOwnProperty('t_url')   &&
  547.         hFeed.hasOwnProperty('camserv') &&
  548.         hFeed.hasOwnProperty('sid')  &&
  549.         hFeed.hasOwnProperty('vidserv') &&
  550.         hFeed.hasOwnProperty('state')   )
  551.     {
  552.         // clear any queued actions before resetting snap frame
  553.         clearTimeout(fadePreviewTimer);
  554.  
  555.         // prepare hFeed for adding or updating existing entry to obsFeeds[]
  556.         var sKeyId = hFeed.sid.toString();
  557.  
  558.         if (obsFeeds.hasOwnProperty(sKeyId))
  559.         {
  560.             if (hFeed.sid.isEqual( selectedKeyId ))
  561.                 selectedKeyId.clear();
  562.  
  563.             if (hFeed.sid.isEqual( attachedFeed ))
  564.             {
  565.                 Log('auto-unattached a feed that was removed')
  566.                 unattachObsFeed(hFeed.sid, "removeFeed: hFeed matching attachedFeed");
  567.             }
  568.  
  569.             delete obsFeeds[sKeyId];
  570.  
  571.             if (Object.size(obsFeeds) > 0)
  572.             {
  573.                 if (selectedKeyId.isNull())
  574.                 {
  575.                     for (sK in obsFeeds)
  576.                     {
  577.                         selectedKeyId.load( JSON.parse(sK) );
  578.                         break;
  579.                     }
  580.                 }
  581.  
  582.                 setFeedLinks();
  583.                 selectFeed(selectedKeyId);
  584.             }
  585.             else resetEmptyFeeds();
  586.         }
  587.     }
  588.  
  589.     return ( ! attachedFeed.isEqual( oldAttachedFeed ) );
  590. }
  591.  
  592. function addFeed(hFeed, hContainer)
  593. {
  594.     hideObj(previewFrame);  // if preview is open, close it
  595.    
  596.     var oldAttachedFeed = null;
  597.     if ( ! attachedFeed.isNull() )
  598.     {
  599.         oldAttachedFeed = new NgxStreamId({ "room": attachedFeed.room, "serv": attachedFeed.serv, "phase": attachedFeed.phase });
  600.     }
  601.     else oldAttachedFeed = new NgxStreamId({"room":0,"serv":0,"phase":"z"});
  602.  
  603.     if (!hFeed.hasOwnProperty('sid') && hFeed.hasOwnProperty('room') && hFeed.hasOwnProperty('camserv') && hFeed.hasOwnProperty('phase'))
  604.     {
  605.         hFeed.sid = new NgxStreamId({ "room": parseInt(hFeed.room), "serv":parseInt(hFeed.camserv), "phase":hFeed.phase });
  606.     }
  607.  
  608.     if (hFeed.hasOwnProperty('t_url')   &&
  609.         hFeed.hasOwnProperty('camserv') &&
  610.         hFeed.hasOwnProperty('sid')  &&
  611.         hFeed.hasOwnProperty('vidserv') &&
  612.         hFeed.hasOwnProperty('state')   )
  613.     {
  614.         // clear any queued actions before resetting snap frame
  615.         clearTimeout(fadePreviewTimer);
  616.  
  617.         // prepare hFeed for adding or updating existing entry to hContainer[]
  618.         var sKeyId = hFeed.sid.toString();
  619.  
  620.         // add text, add to hContainer[], recalculate number of feeds
  621.         hContainer[sKeyId] = hFeed;
  622.  
  623.         if (!hFeed.hasOwnProperty('tag'))
  624.             hFeed.tag = 'OBS Feed ' + sKeyId;
  625.  
  626.         if (!hFeed.hasOwnProperty('mact'))
  627.             hFeed['mact'] = 0;
  628.  
  629.         if (attachedFeed.isEqual( hFeed.sid ))
  630.         {
  631.             if (hFeed.mact == 0)
  632.             {
  633.                 attachedFeed.clear();
  634.                 mfc.UnattachObsFeed( JSON.stringify({"sid": hFeed.sid.toJson(), "updateServer": false }) );
  635.                 Log('AMDBG: attachedFeed matches newly added feed, but new feed has modelactive set to 0, unattaching from it!');
  636.                 mfc.onChangeAttachment();
  637.             }
  638.         }
  639.         else if (attachedFeed.isNull() && hFeed.mact == g_hSelf.sid && g_hSelf.sid > 0)
  640.         {
  641.             Log('AMDBG: attachedFeed is null, but newly added feed has modelactive set to our sessionId, attaching to it!');
  642.             attachedFeed.load( hFeed.sid );
  643.             mfc.onChangeAttachment();
  644.         }
  645.     }
  646.     else Log('hFeed missing one of: t_url, camserv, vidserv, state');
  647.  
  648.     return ( ! attachedFeed.isEqual( oldAttachedFeed ) );
  649. }
  650.  
  651. var fadePreviewTimer = 0;
  652. var thumbCancelTimer = 0;
  653. var thumbRefreshTimer = 0;
  654.  
  655. function setFeedLinks()
  656. {
  657.     var feedCount = Object.size(obsFeeds);
  658.     var prevId = 0, nextId = 0, firstId = 0;
  659.     var nIdx = 0;
  660.  
  661.     for (sK in obsFeeds)
  662.     {
  663.         obsFeeds[sK].prevId = null;
  664.         obsFeeds[sK].nextId = null;
  665.         obsFeeds[sK].idx = ++nIdx;
  666.  
  667.         if (feedCount > 1)
  668.         {
  669.             if (prevId != 0)
  670.             {
  671.                 obsFeeds[prevId].nextId = sK;
  672.                 obsFeeds[sK].prevId = prevId;
  673.             }
  674.             else firstId = sK;
  675.  
  676.             prevId = sK;
  677.         }
  678.     }
  679.  
  680.     if (feedCount > 1 && prevId != 0 && firstId != 0)
  681.     {
  682.         obsFeeds[prevId].nextId = firstId;
  683.         obsFeeds[firstId].prevId = prevId;
  684.     }
  685. }
  686.  
  687. var activeThumbImg, lastThumbImg;
  688.  
  689. function activeThumb_cancel()
  690. {
  691.     var oldTimer = thumbRefreshTimer;
  692.     clearTimeout(thumbRefreshTimer);
  693.     clearTimeout(thumbCancelTimer);
  694.     thumbRefreshTimer = 0;
  695.     thumbCancelTimer = 0;
  696.     thumbRefreshTimer = setTimeout(activeThumb_download, 2000);
  697. }
  698.  
  699. function activeThumb_download()
  700. {
  701.     if (thumbRefreshTimer != 0)
  702.     {
  703.         clearTimeout(thumbRefreshTimer);
  704.         thumbRefreshTimer = 0;
  705.     }
  706.     if (thumbCancelTimer != 0)
  707.     {
  708.         clearTimeout(thumbCancelTimer);
  709.         thumbCancelTimer = 0;
  710.     }
  711.  
  712.  
  713.     if (obsFeeds.hasOwnProperty(selectedKeyId))
  714.     {
  715.         if (obsOpts.obs_live_preview > 0)
  716.         {
  717.             if (obsOpts.ngx_autohide_preview    ==  1                       &&
  718.                 (g_nState                       ==  FCS.FCVIDEO_TX_IDLE ||
  719.                  g_nState                       ==  FCS.FCVIDEO_TX_CLUB  )  &&
  720.                 obsCtx.previewFadeOut          <   0.05                    )
  721.             {
  722.                 renderPreviewDiv('activeBroadcastSnap');
  723.             }
  724.             else
  725.             {
  726.                 activeThumbImg = new Image();
  727.                 activeThumbImg.onload = activeThumb_display;
  728.                 activeThumbImg.src = obsFeeds[selectedKeyId]['t_url'] + '?nc=' + Math.random();
  729.                 activeThumbImg.style.position = "absolute";
  730.                 activeThumbImg.style.top = 0;
  731.                 activeThumbImg.style.left = 0;
  732.                 activeThumbImg.style.zIndex = -100;
  733.                 thumbCancelTimer = setTimeout(activeThumb_cancel, 3000);
  734.             }
  735.         }
  736.     }
  737.     else
  738.     {
  739.         thumbRefreshTimer = setTimeout(activeThumb_download, obsOpts.obs_live_preview);
  740.     }
  741. }
  742.  
  743. function activeThumb_display()
  744. {
  745.     clearTimeout(thumbRefreshTimer);
  746.     clearTimeout(thumbCancelTimer); thumbRefreshTimer = 0;
  747.     thumbCancelTimer = 0;
  748.  
  749.     if (obsOpts.obs_live_preview > 0 || !(activeThumbImg instanceof Image))
  750.     {
  751.         if ( (g_nState                      ==  FCS.FCVIDEO_TX_IDLE ||
  752.               g_nState                      ==  FCS.FCVIDEO_TX_CLUB  )  &&
  753.             obsCtx.previewFadeOut           !=  1.0                     &&
  754.             obsCtx.previewFadeOut            >  0                       &&
  755.             obsOpts.ngx_autohide_preview    ==  1                       )
  756.         {
  757.             activeThumbImg.style.opacity = obsCtx.previewFadeOut;
  758.         }
  759.  
  760.         if (lastThumbImg != undefined)
  761.         {
  762.             overlayFrame.replaceChild(activeThumbImg, lastThumbImg);
  763.             delete lastThumbImg;
  764.         }
  765.         else overlayFrame.appendChild(activeThumbImg);
  766.         lastThumbImg = activeThumbImg;
  767.  
  768.         if ( (g_nState                      ==  FCS.FCVIDEO_TX_IDLE ||
  769.               g_nState                      ==  FCS.FCVIDEO_TX_CLUB  )  &&
  770.             obsCtx.previewFadeOut           >= 0                        &&
  771.             obsOpts.ngx_autohide_preview    ==  1                        )
  772.         {
  773.             // do not refresh thumb, they are live and have autohide preview on
  774.         }
  775.         else
  776.         {
  777.             thumbRefreshTimer = setTimeout(activeThumb_download, obsOpts.obs_live_preview);
  778.         }
  779.     }
  780. }
  781.  
  782. var selectedKeyId = 0;
  783. function selectFeed(sid)
  784. {
  785.     var startLink   = document.getElementById('startLink');
  786.     var startButton = document.getElementById('startButton');
  787.     var feedCount   = Object.size(obsFeeds);
  788.     var sKey        = ((feedCount > 0 && !selectedKeyId.isNull()) ? selectedKeyId.toString() : false);
  789.     var isSelected  = (sKey !== false && obsFeeds.hasOwnProperty(sKey));
  790.     var isAttached  = (isSelected && obsFeeds[sKey].mact == g_hSelf.sid  );
  791.  
  792.     sid             = parseNgxStream(sid);
  793.  
  794.     if (sid instanceof NgxStreamId)
  795.     {
  796.         var sKeyId = sid.toString();
  797.         if (obsFeeds.hasOwnProperty(sKeyId))
  798.         {
  799.             selectedKeyId = sid;
  800.  
  801.             var sStreamType = "Unknown";
  802.             var sStartText = "";
  803.             var sStopText = "";
  804.  
  805.             if (sid.room >= 200000000 && sid.room < 300000000)
  806.             {
  807.                 if      (sid.phase == 'c')  sStreamType = "Club";
  808.                 else if (sid.phase == 'p')  sStreamType = "Private";
  809.                 else if (sid.phase == 'g')  sStreamType = "Group";
  810.                 else                        sStreamType = "InvalidSess";
  811.  
  812.                 sStartText  = "(Start " + sStreamType + "show)";
  813.                 sStopText   = "(End " + sStreamType + "show)";
  814.             }
  815.             else if (sid.room >= 100000000 && sid.room < 200000000)
  816.             {
  817.                 sStreamType = "Public";
  818.                 sStartText = "(Go Live)";
  819.                 sStopText = "(Go Away)";
  820.             }
  821.             else sStreamType = "InvalidRoom";
  822.  
  823.             statusText.innerHTML = sStreamType + ' Stream (' + obsFeeds[sKeyId].idx + ' of ' + feedCount + ')';
  824.  
  825.             var isAttached = (obsFeeds[sKeyId].mact == g_hSelf.sid ? true : false);
  826.  
  827.             var sHtml = "video" + obsFeeds[sKeyId].camserv;
  828.             if (obsFeeds[sKeyId].hasOwnProperty('age'))
  829.                 sHtml += formatSeconds( parseInt(obsFeeds[sKeyId].age) );
  830.             detailText.innerHTML = sHtml;        
  831.        
  832.             sHtml = "<span class=startButton id=startButton><br><center><a class='startLink' id=startLink href='#' ";
  833.             var sRet = (window.parent && window.parent.UI ? "" : "return false;");
  834.             if (!isAttached)
  835.                 sHtml += "onclick='attachObsFeed(" + sKeyId + ");" + sRet + "'>Attach to " + sStreamType + " " + sStartText;
  836.             else
  837.                 sHtml += "onclick='manually_unattachObsFeed(" + sKeyId + ");" + sRet + "'>Unattach from "  + sStreamType + " " + sStopText;
  838.  
  839.             sHtml += "</a></center></span>";
  840.  
  841.             snapImg.innerHTML = sHtml;
  842.             addStyle(overlayFrame, 'frameImgFeed');
  843.    
  844.             // queue download of thumbnail background for this feed now that it's the selected feed
  845.             if (thumbRefreshTimer != 0)
  846.                 clearTimeout(thumbRefreshTimer);
  847.             thumbRefreshTimer = setTimeout(activeThumb_download, 100);
  848.  
  849.             // remove hidden class, if its active
  850.             liveIcon.updateImgStatus();
  851.  
  852.             redrawControls();
  853.         }
  854.         else Log('missing keyId[' + sKeyId + '] from obsFeeds: ' + JSON.stringify( obsFeeds ));
  855.     }
  856.     else Log('sid argument isnt NgxStreamId, call dropped');
  857. }
  858.  
  859. function setLivePreview()
  860. {
  861.     var livePreview = document.getElementById('obs_live_preview');
  862.     obsOpts.obs_live_preview = parseInt( livePreview.options[ livePreview.selectedIndex ].value );
  863.     activeThumb_download();
  864.     mfc.SaveUEOpts( JSON.stringify( { "obs_live_preview": obsOpts.obs_live_preview } ) );
  865. }
  866.  
  867. function onPublishErr(sData)
  868. {
  869.     var objEv = null;
  870.     try
  871.     {
  872.         if ((objEv = JSON.parse(sData)))
  873.         {
  874.             if (objEv.hasOwnProperty("op"))
  875.             {
  876.                 if (objEv.op == "attach")
  877.                 {
  878.                     obsOpts.disableAutoAttach = true;
  879.                     attachedFeed.clear();
  880.                     mfc.onChangeAttachment();
  881.                 }
  882.                 else if (objEv.op == "unattach")
  883.                 {
  884.                     Log('unattach operation failed');
  885.                 }
  886.             }
  887.         }
  888.     }
  889.     catch(e) { Log('onPublishErr error with: ' + JSON.stringify(e)); }
  890. }
  891.  
  892. function onFeedStateEvents(sData)
  893. {
  894.     var trimKeys = [ 'prevId', 'nextId', 'idx', 'age', 'exp' ];
  895.     var attachmentChanged = false;
  896.     var objEv = null;
  897.  
  898.     try
  899.     {
  900.         if ((objEv = JSON.parse(sData)))
  901.         {
  902.             if (isArray(objEv))
  903.             {
  904.                 newObsFeeds = { };
  905.                 var sKeyId = '';
  906.  
  907.                 for (var n = 0; n < objEv.length; n++)
  908.                 {
  909.                     if (isObject(objEv[n]) && objEv[n].hasOwnProperty('state') && objEv[n].hasOwnProperty('op') && objEv[n].hasOwnProperty('room'))
  910.                     {
  911.                         if (!(objEv[n].op & FCS.FCCHAN_PART))
  912.                         {
  913.                             objEv[n].sid = new NgxStreamId({ "room": parseInt(objEv[n].room), "serv": parseInt(objEv[n].camserv), "phase": objEv[n].phase });
  914.                            
  915.                             if (sKeyId == '')
  916.                                 sKeyId = objEv[n].sid.toString();
  917.  
  918.                             if (addFeed(objEv[n], newObsFeeds))
  919.                                 attachmentChanged = true;
  920.                         }
  921.                     }
  922.                 }
  923.  
  924.                 var newSz = Object.size(newObsFeeds);
  925.                 var oldSz = Object.size(obsFeeds);
  926.                 var anyChanges = (oldSz != newSz);
  927.                 if (oldSz === newSz)
  928.                 {
  929.                     var hOrig = JSON.parse( JSON.stringify( obsFeeds ) );
  930.                     var hNew = JSON.parse( JSON.stringify( newObsFeeds ) );
  931.                     for (n in hOrig)
  932.                         for (x in trimKeys)
  933.                             delete hOrig[n][ trimKeys[x] ];
  934.                     for (n in hNew)
  935.                         for (x in trimKeys)
  936.                             delete hNew[n][ trimKeys[x] ];
  937.                     var sOrig = JSON.stringify( hOrig );
  938.                     var sNew = JSON.stringify( hNew );
  939.                     anyChanges = (sOrig !== sNew);
  940.                 }
  941.  
  942.                 // redraw/rebuild only if some relevant changes made to obsFeeds
  943.                 if (anyChanges || attachmentChanged)
  944.                 {
  945.                     // change in obsFeeds, reset obsFeeds to newObsFeeds and then reprocess attachments
  946.                     obsFeeds = newObsFeeds;
  947.                
  948.                     // if the number of feeds changed and we are in either Away or RX idle, then
  949.                     // we auto-un attach any currently attached feeds.
  950.                     if (g_nState  === FCS.FCVIDEO_TX_AWAY || g_nState === FCS.FCVIDEO_RX_IDLE)
  951.                     {
  952.                         if (oldSz !== newSz)
  953.                         {
  954.                             if (!attachedFeed.isNull())
  955.                             {
  956.                                 unattachObsFeed(attachedFeed, "onFeedStateEvents: change in feeds while idle/away");
  957.                                 attachmentChanged = true;
  958.                                 attachedFeed.clear();
  959.                             }
  960.                         }
  961.                     }
  962.  
  963.                     // if our attached feed is no longer found in obsFeeds, also unattach it
  964.                     if (!attachedFeed.isNull() && !obsFeeds.hasOwnProperty(attachedFeed.toString()))
  965.                     {
  966.                         unattachObsFeed(attachedFeed, "onFeedStateEvents: obsFeeds missing attachedFeed");
  967.                         attachmentChanged = true;
  968.                         attachedFeed.clear();
  969.                     }
  970.  
  971.                     // if our selectedFeed is no longer in obsFeeds, clear the currently selected index
  972.                     if ( ! selectedKeyId.isNull() )
  973.                         if ( ! obsFeeds.hasOwnProperty( selectedKeyId.toString() ) )
  974.                             selectedKeyId.clear();
  975.  
  976.                     // if we have an attachment but no selected feed, set selected feed to that
  977.                     // attachment, otherwise we point sKeyId to the currently selected feed. We
  978.                     // reselect that feed (sKeyId) next, provided obsFeeds isnt empty.
  979.                     if ( selectedKeyId.isNull() )
  980.                     {
  981.                         if ( ! attachedFeed.isNull() )
  982.                             if ( obsFeeds.hasOwnProperty( attachedFeed.toString() ) )
  983.                                 sKeyId = attachedFeed.toString();
  984.                     }
  985.                     else sKeyId = selectedKeyId.toString();
  986.  
  987.                     // if we have at least 1 feed, then re-select current feed in case attachment
  988.                     // status changed or selected key id moved/reset. Otherwise reset status to
  989.                     // empty feed mode when no feeds present.
  990.                     if (Object.size(obsFeeds) > 0)
  991.                     {
  992.                         setFeedLinks();
  993.                         selectFeed(sKeyId);
  994.                     }
  995.                     else resetEmptyFeeds();
  996.                 }
  997.                 else
  998.                 {
  999.                     //Log('onFeedStates: no relevant changes in obsFeeds or attachedFeed status');
  1000.                     obsFeeds = newObsFeeds;
  1001.                     setFeedLinks();
  1002.                 }
  1003.             }
  1004.  
  1005.             else if (isObject(objEv))
  1006.             {
  1007.                 Log('BUG: Received [deprecated] single feedstate object');
  1008.                 if (    objEv.hasOwnProperty('state')
  1009.                     &&  objEv.hasOwnProperty('sid')
  1010.                     &&  objEv.hasOwnProperty('op')  )
  1011.                 {
  1012.                     if (objEv.op & FCS.FCCHAN_PART)
  1013.                     {
  1014.                         if ( removeFeed(objEv, obsFeeds) )
  1015.                             attachmentChanged = true;
  1016.                     }
  1017.                     else if (objEv.op & FCS.FCCHAN_JOIN)
  1018.                     {
  1019.                         if ( addFeed(objEv, obsFeeds) )
  1020.                             attachmentChanged = true;
  1021.                     }
  1022.                 }
  1023.             }
  1024.         }
  1025.     }
  1026.     catch(e) { Log('onFeedStateEvents error with: ' + JSON.stringify(e)); }
  1027.  
  1028.     if (attachmentChanged)
  1029.         mfc.onChangeAttachment();
  1030.  
  1031.     if (Object.size(obsFeeds) > 0)
  1032.     {
  1033.         var sHtml = '';
  1034.         var hOpts = [   { "val": 0, "label": "Disable preview images." },
  1035.                         { "val": 10, "label": "Quickly as possible." },
  1036.                         { "val": 250, "label": "4 frames per sec." },
  1037.                         { "val": 500, "label": "2 frames per sec." },
  1038.                         { "val": 1000, "label": "1 frame per sec." },
  1039.                         { "val": 2000, "label": "1 frame per 2 sec." },
  1040.                         { "val": 5000, "label": "1 frame per 5 sec." }   ];
  1041.  
  1042.  
  1043.         sHtml  = "<span class=obsHidePreviewLabel>Preview image display:</span> <br> ";
  1044.  
  1045.         sHtml += "<select id=obs_live_preview onchange='setLivePreview();' class=obsLiveSelect>";
  1046.         for (var n = 0; n < hOpts.length; n++)
  1047.             sHtml += "<option class=obsLiveOption value="
  1048.                   +  hOpts[n].val + " "
  1049.                   +  (obsOpts.obs_live_preview == hOpts[n].val ? "selected" : "")
  1050.                   +  ">" + hOpts[n].label;
  1051.         sHtml += "</select><br>";
  1052.  
  1053.         sHtml += "<input type=checkbox class=obsHidePreview id=obs_hide_preview " + (obsOpts.ngx_autohide_preview == 0 ? "" : "checked") + ">"
  1054.               +  "<span class=obsHidePreviewLabel><a href='#' onclick='hidePreviewToggle();'>"
  1055.               +  "Hide preview when Live</a></span>";
  1056.  
  1057.         thumbPreviewText.innerHTML = sHtml;
  1058.         statusBox.style.height = "142px";
  1059.         showObj(hideStatus);
  1060.     }
  1061.     else
  1062.     {
  1063.         thumbPreviewText.innerHTML = "";
  1064.         renderPreviewDiv('emptySnap');
  1065.         showObj(modelStatus);
  1066.         hideObj(hideStatus);
  1067.         hideObj(statusBox);
  1068.     }
  1069. }
  1070.  
  1071. function hidePreviewToggle()
  1072. {
  1073.     hideCheckbox = document.getElementById('obs_hide_preview');
  1074.     hideCheckbox.checked = !hideCheckbox.checked;
  1075.     obsOpts.ngx_autohide_preview = (hideCheckbox.checked ? 1 : 0);
  1076.     mfc.SaveUEOpts( JSON.stringify( { "ngx_autohide_preview": obsOpts.ngx_autohide_preview } ) );
  1077. }
  1078.  
  1079. function renderPreviewDiv(sClass)
  1080. {
  1081.     activeThumbImg = buildPreviewDiv(sClass);
  1082.  
  1083.     activeThumb_display();
  1084. }
  1085.  
  1086. function buildPreviewDiv(sClass)
  1087. {
  1088.     var div = document.createElement('div');
  1089.     div.className = sClass;
  1090.  
  1091.     div.style.position = "absolute";
  1092.     div.style.top = 0;
  1093.     div.style.left = 0;
  1094.     div.style.zIndex = -100;
  1095.     return div;
  1096. }
  1097.  
  1098. function resetEmptyFeeds()
  1099. {
  1100.     obsFeeds = { };
  1101.     selectedKeyId = new NgxStreamId({"room":0,"serv":0,"phase":"z"});
  1102.  
  1103.     detailText.innerHTML = "Start streaming in OBS Studio.";
  1104.     statusText.innerHTML = "Waiting for OBS Feeds.";
  1105.     thumbPreviewText.innerHTML = "";
  1106.     snapImg.innerHTML = "";
  1107.  
  1108.     overlayFrame.style.backgroundImage = "none;";
  1109.     overlayFrame.style.backgroundColor = "transparent";
  1110.     dropStyle(overlayFrame, 'frameImgFeed');
  1111.  
  1112.     dropStyle(nextFeed, 'activeNextFeed');
  1113.     addStyle(nextFeed, 'inactiveNextFeed');
  1114.     hideObj(nextFeed);
  1115.    
  1116.     dropStyle(prevFeed, 'activePrevFeed');
  1117.     addStyle(prevFeed, 'inactivePrevFeed');
  1118.     hideObj(prevFeed);
  1119.  
  1120.     alwaysShowControls = false;
  1121.     //showObj(modelStatus);
  1122.  
  1123.     // make sure we can see the 'waiting for obs feeds...' banner
  1124.     //showObj(statusBox);
  1125.  
  1126.     liveIcon.updateImgStatus();
  1127.     redrawControls();
  1128. }
  1129.  
  1130. function redrawControls()
  1131. {
  1132.     // update if the links for previous, next, and attach/unattach
  1133.     // should be hidden or visible based on attach state and number
  1134.     // of obs feeds available
  1135.     //
  1136.     var feedCount   = Object.size(obsFeeds);
  1137.     var startLink   = document.getElementById('startLink');
  1138.     var startButton = document.getElementById('startButton');
  1139.     var sKey        = ((feedCount > 0 && !selectedKeyId.isNull()) ? selectedKeyId.toString() : false);
  1140.     var isSelected  = (sKey !== false && obsFeeds.hasOwnProperty(sKey));
  1141.     var isAttached  = (isSelected && obsFeeds[sKey].mact == g_hSelf.sid  );
  1142.     var hasNext     = (isSelected && obsFeeds[sKey].nextId);
  1143.     var hasPrev     = (isSelected && obsFeeds[sKey].prevId);
  1144.  
  1145.     dropStyle( nextFeed, (hasNext ? 'inactiveNextFeed' :  'activeNextFeed'  ) );
  1146.     addStyle(  nextFeed, (hasNext ?  'activeNextFeed'  : 'inactiveNextFeed' ) );
  1147.     dropStyle( prevFeed, (hasPrev ? 'inactivePrevFeed' :  'activePrevFeed'  ) );
  1148.     addStyle(  prevFeed, (hasPrev ?  'activePrevFeed'  : 'inactivePrevFeed' ) );
  1149.  
  1150.     // hide next/previous links when attached, or when we only
  1151.     // have 1 feed to choose from. If there are more feeds, model
  1152.     // must unattach before getting these navigation links to browse
  1153.     // snapshots. Name of function assigned to sFunc, call that
  1154.     // function on both prevFeed and nextFeed as arguments.
  1155.     //
  1156.     var sFunc = (feedCount == 1 || isAttached) ? "hideObj" : "showObj";
  1157.     execFuncByName(sFunc, "", prevFeed);
  1158.     execFuncByName(sFunc, "", nextFeed);
  1159.  
  1160.     // hide the attach/unattach links when we're attached and
  1161.     // dont have the alwaysShowControls flag set, but show them
  1162.     // if we are not attached or, but
  1163.     // show them if we're not attached or the
  1164.    
  1165.     // can always force toggle them back on by clicking the liveicon.
  1166.     // if model clicks live icon to view these, alwaysShowControls
  1167.     // is set and they are then pinned open/visible, otherwise only
  1168.     // visible when no feed is attached.
  1169.     //
  1170.     sFunc = (alwaysShowControls || !isAttached) ? "showObj" : "hideObj";
  1171.     execFuncByName(sFunc, "", startLink);
  1172.  
  1173.     if (startButton)
  1174.     {
  1175.         execFuncByName(sFunc, "", startButton);
  1176.         startButton.style.background = "rgba(66, 66, 66, " + ((alwaysShowControls || !isAttached) ? "0.30" : "0.00") + ")";
  1177.     }
  1178.  
  1179.     // status box/modelStatus is directly tied to show controls flag or
  1180.     // when feed count is 0.  modelStatus is always opposite from statusBox.
  1181.     //
  1182.     hideObj( (alwaysShowControls || feedCount < 1) ? modelStatus :  statusBox  );
  1183.     showObj( (alwaysShowControls || feedCount < 1) ?  statusBox  : modelStatus );
  1184.  
  1185.     renderSessionState(g_nState);
  1186.  
  1187.     statusBox.style.opacity = (isAttached ? .88 : 1);
  1188.     liveIcon.updateImgStatus();
  1189. }
  1190.  
  1191.  
  1192. liveIcon.onmouseenter = function()
  1193. {
  1194.     liveIcon.updateImgStatus();
  1195.    
  1196.     if (liveIcon.fadeTimer != 0)
  1197.     {
  1198.         clearTimeout(liveIcon.fadeTimer);
  1199.         liveIcon.fadeTimer = 0;
  1200.     }
  1201.     semiFadeIn.clearQueuedFrames();
  1202.     semiFadeOut.clearQueuedFrames();
  1203.  
  1204.  
  1205.     liveIcon.fadeTimer = setTimeout( function() { semiFadeIn(liveIcon); }, 10);
  1206. };
  1207. liveIcon.onmouseleave = function()
  1208. {
  1209.     liveIcon.updateImgStatus();
  1210.     if (liveIcon.fadeTimer != 0)
  1211.     {
  1212.         clearTimeout(liveIcon.fadeTimer);
  1213.         liveIcon.fadeTimer = 0;
  1214.     }
  1215.     semiFadeIn.clearQueuedFrames();
  1216.     semiFadeOut.clearQueuedFrames();
  1217.     liveIcon.fadeTimer = setTimeout( function() { semiFadeOut(liveIcon); }, 100);
  1218. };
  1219.  
  1220. liveIcon.updateImgStatus = function()
  1221. {
  1222.     var feedCount   = Object.size(obsFeeds);
  1223.     var sKey        = ((feedCount > 0 && !selectedKeyId.isNull()) ? selectedKeyId.toString() : false);
  1224.     var isSelected  = (sKey !== false && obsFeeds.hasOwnProperty(sKey));
  1225.     var isAttached  = (isSelected && obsFeeds[sKey].mact == g_hSelf.sid  );
  1226.  
  1227.     // make sure to add the right activated/attached styles to liveIcon,
  1228.     // remove any of the wrong inactive/unattached styles from liveIcon
  1229.     //
  1230.     addStyle(  liveIcon, 'live_' + (isAttached ?  'active'  : 'inactive') + '_icon' );
  1231.     dropStyle( liveIcon, 'live_' + (isAttached ? 'inactive' :  'active')  + '_icon' );
  1232.  
  1233.     // show icon if feed count non-zero, otherwise hide it
  1234.     execFuncByName( (feedCount > 0 ? "showObj" : "hideObj"), "", liveIcon );
  1235. }
  1236.  
  1237. // initialize controls, draw controls
  1238. resetEmptyFeeds();
  1239.  
  1240. // change state from defaulted tx_idle to offline until we get our actual state data from parent
  1241. onSessionState( JSON.stringify( {"vs":FCS.FCVIDEO_RX_IDLE} ) );
  1242.  
  1243. // each second evaluate if we need to auto-attach to a feed based on our auto-attach settings
  1244. setInterval(function()
  1245. {
  1246.     if (attachedFeed.isNull())
  1247.     {
  1248.         unattachedInterval++;
  1249.         attachedInterval = 0;
  1250.  
  1251.         // auto-attach? first check if its enabled and there is at least one feed
  1252.         var nFeedSz = Object.size(obsFeeds);
  1253.         if (obsOpts.obs_autoattach & 1 && nFeedSz > 0)
  1254.         {
  1255.             if (obsOpts.disableAutoAttach == false)
  1256.             {
  1257.                 // if only one feed, attach to it.  If more than one feed, attach to either the oldest
  1258.                 // (if auto attach oldest bit is set), or the newest (if newest bit is set), or if neither
  1259.                 // are set, then don't auto-attach.
  1260.                 //
  1261.                 if      (nFeedSz == 1)                  autoAttachFirst();
  1262.                 else if (obsOpts.obs_autoattach & 4)    autoAttachOldest();
  1263.                 else if (obsOpts.obs_autoattach & 8)    autoAttachNewest();
  1264.                 else                                    Log('No auto-attach handler called, feedSz: ' + nFeedSz);
  1265.             }
  1266.         }
  1267.     }
  1268.     else
  1269.     {
  1270.         unattachedInterval = 0;
  1271.         attachedInterval++;
  1272.     }
  1273. }, 1000);
  1274.  
  1275. function autoAttachFirst()
  1276. {
  1277.     // auto-attach to first feed in obsFeeds
  1278.     var sKey = false;
  1279.  
  1280.     for (sK in obsFeeds)
  1281.         if (obsFeeds[sK].hasOwnProperty('age') && sKey === false)
  1282.             sKey = sK;
  1283.  
  1284.     if (sKey !== false)
  1285.     {
  1286.         Log('[autoAttach first] attached to ' + sKey + ', age: ' + parseInt(obsFeeds[sKey].age) + ' sec');
  1287.         attachObsFeed(sKey);
  1288.     }
  1289. }
  1290.  
  1291. function autoAttachOldest()
  1292. {
  1293.     // auto-attach to oldest
  1294.     var sKey = (selectedKeyId.isNull() ? false : selectedKeyId.toString());
  1295.     var nVal = 0;
  1296.  
  1297.     for (sK in obsFeeds)
  1298.     {
  1299.         if (obsFeeds[sK].hasOwnProperty('age'))
  1300.         {
  1301.             if (nVal <= parseInt(obsFeeds[sK].age))
  1302.             {
  1303.                 nVal = parseInt(obsFeeds[sK].age);
  1304.                 sKey = sK;
  1305.             }
  1306.         }
  1307.     }
  1308.  
  1309.     if (sKey !== false)
  1310.     {
  1311.         Log('[autoAttach oldest] attached to ' + sKey + ', age: ' + parseInt(obsFeeds[sKey].age) + ' sec');
  1312.         attachObsFeed(sKey);
  1313.     }
  1314. }
  1315.  
  1316. function autoAttachNewest()
  1317. {
  1318.     // auto-attach to newest
  1319.     var sKey = (selectedKeyId.isNull() ? false : selectedKeyId.toString());                        
  1320.     var nVal = 999999999;
  1321.  
  1322.     for (sK in obsFeeds)
  1323.     {
  1324.         if (obsFeeds[sK].hasOwnProperty('age'))
  1325.         {
  1326.             if (nVal > parseInt(obsFeeds[sK].age))
  1327.             {
  1328.                 nVal = parseInt(obsFeeds[sK].age);
  1329.                 sKey = sK;
  1330.             }
  1331.         }
  1332.     }
  1333.  
  1334.     if (sKey !== false)
  1335.     {
  1336.         Log('[autoAttach newest] attached to ' + sKey + ', age: ' + parseInt(obsFeeds[sKey].age) + ' sec');
  1337.         attachObsFeed(sKey);
  1338.     }
  1339. }
  1340.  
  1341. function addStyle(obj, someClass)
  1342. {
  1343.     if (obj != undefined)
  1344.         if (!obj.classList.contains(someClass))
  1345.             obj.classList.add(someClass);
  1346. }
  1347. function dropStyle(obj, someClass)
  1348. {
  1349.     if (obj != undefined)
  1350.         if (obj.classList.contains(someClass))
  1351.             obj.classList.remove(someClass);
  1352. }
  1353.  
  1354. function showObj(obj) { dropStyle(obj, "hidden"); }
  1355. function hideObj(obj) {  addStyle(obj, "hidden"); }
  1356.  
  1357. function execFuncByName(functionName, context /*, ... */ )
  1358. {
  1359.     var args = Array.prototype.slice.call(arguments, 2);
  1360.     return window[functionName].apply(context, args);
  1361. }
  1362.  
  1363. function formatSeconds(nSec)
  1364. {
  1365.     var nMin = 0, nHour = 0, nDay = 0;
  1366.     var sHtml = "<span class=feedTime>";
  1367.     var nBaseLen = sHtml.length;
  1368.                
  1369.     while (nSec >= 86400)
  1370.     {
  1371.         nSec -= 86400;
  1372.         nDay++;
  1373.     }
  1374.  
  1375.     while (nSec >= 3600)
  1376.     {
  1377.         nSec -= 3600;
  1378.         nHour++;
  1379.     }
  1380.  
  1381.     while (nSec >= 60)
  1382.     {
  1383.         nSec -= 60;
  1384.         nMin++;
  1385.     }
  1386.  
  1387.     if (nDay > 0)
  1388.         sHtml += nDay + " day" + (nDay > 1 ? "s" : "");
  1389.     if (nHour > 0)
  1390.         sHtml += (sHtml.length > nBaseLen ? ", " : "") + nHour + " hour" + (nHour > 1 ? "s" : "");
  1391.     if (nMin > 0)
  1392.         sHtml += (sHtml.length > nBaseLen ? ", " : "") + nMin + " min";
  1393.  
  1394.     // only render seconds if the feed is < 1 min old
  1395.     if (nDay == 0 && nHour == 0 && nMin == 0 && nSec > 0)
  1396.         sHtml += (sHtml.length > nBaseLen ? ", " : "") + nSec + " sec";
  1397.    
  1398.     sHtml += "</span>";
  1399.  
  1400.     return sHtml;
  1401. }
  1402.  
  1403. function getStackTrace ()
  1404. {
  1405.     var stack;
  1406.     try { throw new Error(''); }
  1407.     catch (error) { stack = error.stack || ''; }
  1408.     stack = stack.split('\n').map(function (line) { return line.trim(); });
  1409.     return stack.splice(stack[0] == 'Error' ? 2 : 1);
  1410. }
  1411.  
  1412. </script>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement