diff --git a/build-extras.gradle b/build-extras.gradle index 4a894f9d..b995e892 100644 --- a/build-extras.gradle +++ b/build-extras.gradle @@ -22,5 +22,4 @@ dependencies { compile 'com.android.support:appcompat-v7:23.1.0' compile 'com.android.support:design:23.0.0' compile 'com.android.volley:volley:1.1.0' - compile 'com.github.ksoichiro:android-observablescrollview:1.6.0' } diff --git a/plugin.xml b/plugin.xml index 04f2748c..fdad4be6 100644 --- a/plugin.xml +++ b/plugin.xml @@ -9,10 +9,12 @@ Apache 2.0 opentok,tokbox + + - + @@ -78,11 +80,14 @@ - + + + + @@ -97,5 +102,5 @@ - + diff --git a/src/android/OpenTokAndroidPlugin.java b/src/android/OpenTokAndroidPlugin.java index 58ca94d2..2cbc66dc 100644 --- a/src/android/OpenTokAndroidPlugin.java +++ b/src/android/OpenTokAndroidPlugin.java @@ -46,8 +46,6 @@ import com.opentok.android.SubscriberKit; import com.opentok.android.BaseVideoRenderer; -import com.github.ksoichiro.android.observablescrollview.ObservableScrollView; - public class OpenTokAndroidPlugin extends CordovaPlugin implements Session.SessionListener, Session.ConnectionListener, @@ -73,12 +71,15 @@ public class OpenTokAndroidPlugin extends CordovaPlugin public static final String[] perms = {Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}; public CallbackContext permissionsCallback; - public class RunnableUpdateViews implements Runnable { public JSONArray mProperty; public View mView; public ArrayList allStreamViews; + // Used for setting the camera views. + public float widthRatio; + public float heightRatio; + public class CustomComparator implements Comparator { @Override public int compare(RunnableUpdateViews object1, RunnableUpdateViews object2) { @@ -119,6 +120,18 @@ public int getZIndex() { } } + public void setPosition() { + try { + mView.setX(mProperty.getInt(2) * widthRatio); + mView.setY(mProperty.getInt(1) * heightRatio); + + ViewGroup.LayoutParams params = mView.getLayoutParams(); + params.width = (int) (mProperty.getInt(3) * widthRatio); + params.height = (int) (mProperty.getInt(4) * heightRatio); + mView.setLayoutParams(params); + } catch (Exception e) {} + } + @SuppressLint("NewApi") @Override public void run() { @@ -126,8 +139,6 @@ public void run() { Log.i(TAG, "updating view in ui runnable" + mProperty.toString()); Log.i(TAG, "updating view in ui runnable " + mView.toString()); - float widthRatio, heightRatio; - // Ratios are index 6 & 7 on TB.updateViews, 8 & 9 on subscribe event, and 9 & 10 on TB.initPublisher int ratioIndex; if (mProperty.get(6) instanceof Number) { @@ -143,12 +154,7 @@ public void run() { widthRatio = (float) mProperty.getDouble(ratioIndex) * metrics.density; heightRatio = (float) mProperty.getDouble(ratioIndex + 1) * metrics.density; - mView.setY(mProperty.getInt(1) * heightRatio); - mView.setX(mProperty.getInt(2) * widthRatio); - ViewGroup.LayoutParams params = mView.getLayoutParams(); - params.height = (int) (mProperty.getInt(4) * heightRatio); - params.width = (int) (mProperty.getInt(3) * widthRatio); - mView.setLayoutParams(params); + setPosition(); updateZIndices(); } catch (Exception e) { Log.i(TAG, "error when trying to retrieve properties while resizing properties"); @@ -421,22 +427,6 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) { // Make the web view transparent. _webView.getView().setBackgroundColor(Color.argb(1, 0, 0, 0)); - - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - // original layout - ViewGroup frameLayout = (ViewGroup) _webView.getView().getParent(); - ViewGroup layoutParent = (ViewGroup) frameLayout.getParent(); - layoutParent.removeView(frameLayout); - - // Observable scroll view with webView and cameraViews as children - ObservableScrollView scrollView = new ObservableScrollView(_cordova.getActivity().getApplicationContext()); - scrollView.addView(frameLayout); - layoutParent.addView(scrollView); - } - }); - Log.d(TAG, "Initialize Plugin"); // By default, get a pointer to mainView and add mainView to the viewList as it always exists (hold cordova's view) if (!viewList.has("mainView")) { @@ -586,6 +576,30 @@ public void run() { cordova.getActivity().runOnUiThread(runsub); } } + } else if (action.equals("hasStatusBarPlugin")) { + // Currently only needed for iOS, but for the sake of sanity we include it here aswell. + } else if (action.equals("updateCamera")) { + int top = args.getInt(1); + int left = args.getInt(2); + int width = args.getInt(3); + int height = args.getInt(4); + + if (args.getString(0).equals("TBPublisher") && myPublisher != null && sessionConnected) { + myPublisher.mProperty.put(1, top); + myPublisher.mProperty.put(2, left); + myPublisher.mProperty.put(3, width); + myPublisher.mProperty.put(4, height); + + myPublisher.setPosition(); + } else { + RunnableSubscriber runsub = subscriberCollection.get(args.getString(0)); + runsub.mProperty.put(1, top); + runsub.mProperty.put(2, left); + runsub.mProperty.put(3, width); + runsub.mProperty.put(4, height); + + runsub.setPosition(); + } } else if (action.equals("exceptionHandler")) { } diff --git a/src/ios/OpenTokPlugin.m b/src/ios/OpenTokPlugin.m index eb507e5d..e7d2188e 100644 --- a/src/ios/OpenTokPlugin.m +++ b/src/ios/OpenTokPlugin.m @@ -17,6 +17,7 @@ @implementation OpenTokPlugin{ NSMutableDictionary *callbackList; NSString *apiKey; NSString *sessionId; + Boolean statusBarPlugin; } @synthesize exceptionId; @@ -28,6 +29,7 @@ -(void) pluginInitialize{ [self.webView setOpaque:false]; [self.webView setBackgroundColor:UIColor.clearColor]; + statusBarPlugin = true; callbackList = [[NSMutableDictionary alloc] init]; } - (void)addEvent:(CDVInvokedUrlCommand*)command{ @@ -164,8 +166,9 @@ - (void)initPublisher:(CDVInvokedUrlCommand *)command{ [_publisher setPublishAudio:bpubAudio]; [_publisher setPublishVideo:bpubVideo]; [_publisher setAudioFallbackEnabled:baudioFallbackEnabled]; - [self.webView.scrollView addSubview:_publisher.view]; - [_publisher.view setFrame:CGRectMake(left, top, width, height)]; + [self.webView.superview addSubview:_publisher.view]; + + [self setPosition: @"TBPublisher" top: top left: left width: width height: height]; // Set depth location of camera view based on CSS z-index. _publisher.view.layer.zPosition = zIndex; @@ -187,9 +190,11 @@ - (void)updateView:(CDVInvokedUrlCommand*)command{ int width = [[command.arguments objectAtIndex:3] intValue]; int height = [[command.arguments objectAtIndex:4] intValue]; int zIndex = [[command.arguments objectAtIndex:5] intValue]; + if ([sid isEqualToString:@"TBPublisher"]) { NSLog(@"The Width is: %d", width); - _publisher.view.frame = CGRectMake(left, top, width, height); + // Reposition the video feeds! + [self setPosition: sid top: top left: left width: width height: height]; // Set depth location of camera view based on CSS z-index. _publisher.view.layer.zPosition = zIndex; @@ -197,7 +202,7 @@ - (void)updateView:(CDVInvokedUrlCommand*)command{ // If the zIndex is 0(default) bring the view to the top, last one wins. // See: https://github.com/saghul/cordova-plugin-iosrtc/blob/5b6a180b324c8c9bac533fa481a457b74183c740/src/PluginMediaStreamRenderer.swift#L191 if(zIndex == 0) { - [self.webView.scrollView bringSubviewToFront:_publisher.view]; + [self.webView.superview bringSubviewToFront:_publisher.view]; } } @@ -206,7 +211,7 @@ - (void)updateView:(CDVInvokedUrlCommand*)command{ if (streamInfo) { // Reposition the video feeds! - streamInfo.view.frame = CGRectMake(left, top, width, height); + [self setPosition: sid top: top left: left width: width height: height]; // Set depth location of camera view based on CSS z-index. streamInfo.view.layer.zPosition = zIndex; @@ -214,7 +219,7 @@ - (void)updateView:(CDVInvokedUrlCommand*)command{ // If the zIndex is 0(default) bring the view to the top, last one wins. // See: https://github.com/saghul/cordova-plugin-iosrtc/blob/5b6a180b324c8c9bac533fa481a457b74183c740/src/PluginMediaStreamRenderer.swift#L191 if(zIndex == 0) { - [self.webView.scrollView bringSubviewToFront:_publisher.view]; + [self.webView.superview bringSubviewToFront:_publisher.view]; } } @@ -223,6 +228,35 @@ - (void)updateView:(CDVInvokedUrlCommand*)command{ //[self.commandDelegate sendPluginResult:callbackResult toSuccessCallbackString:command.callbackId]; [self.commandDelegate sendPluginResult:callbackResult callbackId:command.callbackId]; } +- (void)hasStatusBarPlugin:(CDVInvokedUrlCommand*)command{ + statusBarPlugin = [[command.arguments objectAtIndex:0] boolValue]; +} +- (void)updateCamera:(CDVInvokedUrlCommand*)command{ + NSString* sid = [command.arguments objectAtIndex:0]; + int top = [[command.arguments objectAtIndex:1] intValue]; + int left = [[command.arguments objectAtIndex:2] intValue]; + int width = [[command.arguments objectAtIndex:3] intValue]; + int height = [[command.arguments objectAtIndex:4] intValue]; + + [self setPosition: sid top: top left: left width: width height: height]; +} +- (void)setPosition:(NSString*)sid top:(int)top left:(int)left width:(int)width height:(int)height { + int offsetTop = 20; + if (statusBarPlugin) { + // We set the offsetTop to the top position of the webView because the StatusBarPlugin changes the top position to the proper offset. + offsetTop = self.webView.frame.origin.y; + } else if ([UIApplication sharedApplication].isStatusBarHidden) { + offsetTop = 0; + } + + CGRect frame = CGRectMake(left, top + offsetTop, width, height); + if ([sid isEqualToString:@"TBPublisher"]) { + _publisher.view.frame = frame; + } else { + OTSubscriber* streamInfo = [subscriberDictionary objectForKey:sid]; + streamInfo.view.frame = frame; + } +} #pragma mark Publisher Methods - (void)publishAudio:(CDVInvokedUrlCommand*)command{ @@ -331,7 +365,7 @@ - (void)subscribe:(CDVInvokedUrlCommand*)command{ // Set depth location of camera view based on CSS z-index. sub.view.layer.zPosition = zIndex; - [self.webView.scrollView addSubview:sub.view]; + [self.webView.superview addSubview:sub.view]; // Return to JS event handler CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; diff --git a/src/js/OT.coffee b/src/js/OT.coffee index f4ce1b93..7d3935eb 100644 --- a/src/js/OT.coffee +++ b/src/js/OT.coffee @@ -59,3 +59,6 @@ window.addEventListener "orientationchange", (-> ), 1000 return ), false +document.addEventListener "ondeviceready", (-> + Cordova.exec(TBSuccess, TBError, OTPlugin, "hasStatusBarPlugin", [window.hasOwnProperty("StatusBar")] ) +), false \ No newline at end of file diff --git a/src/js/OTHelpers.coffee b/src/js/OTHelpers.coffee index 2e27d3e3..c2876d0b 100644 --- a/src/js/OTHelpers.coffee +++ b/src/js/OTHelpers.coffee @@ -7,35 +7,25 @@ streamElements = {} # keep track of DOM elements for each stream # # Helper methods # -getPosition = (divName) -> +getPosition = (pubDiv) -> # Get the position of element - pubDiv = document.getElementById(divName) if !pubDiv then return {} - computedStyle = if window.getComputedStyle then getComputedStyle(pubDiv, null) else {} - width = pubDiv.offsetWidth - height = pubDiv.offsetHeight - curtop = pubDiv.offsetTop - curleft = pubDiv.offsetLeft - while(pubDiv = pubDiv.offsetParent) - curleft += pubDiv.offsetLeft - curtop += pubDiv.offsetTop - return { - top:curtop - left:curleft - width:width - height:height - } - -replaceWithVideoStream = (divName, streamId, properties) -> + return pubDiv.getBoundingClientRect() + +replaceWithVideoStream = (element, streamId, properties) -> typeClass = if streamId == PublisherStreamId then PublisherTypeClass else SubscriberTypeClass - element = document.getElementById(divName) - element.setAttribute( "class", "OT_root #{typeClass}" ) - element.setAttribute( "data-streamid", streamId ) - element.style.width = properties.width+"px" - element.style.height = properties.height+"px" - element.style.overflow = "hidden" - element.style['background-color'] = "#000000" - streamElements[ streamId ] = element + if (properties.insertMode == "replace") + newElement = element + else + newElement = document.createElement( "div" ) + newElement.setAttribute( "class", "OT_root #{typeClass}" ) + newElement.setAttribute( "data-streamid", streamId ) + newElement.setAttribute( "data-insertMode", properties.insertMode ) + newElement.style.width = properties.width+"px" + newElement.style.height = properties.height+"px" + newElement.style.overflow = "hidden" + newElement.style['background-color'] = "#000000" + streamElements[ streamId ] = newElement internalDiv = document.createElement( "div" ) internalDiv.setAttribute( "class", VideoContainerClass) @@ -50,8 +40,15 @@ replaceWithVideoStream = (divName, streamId, properties) -> # todo: js change styles or append css stylesheets? Concern: users will not be able to change via css internalDiv.appendChild( videoElement ) - element.appendChild( internalDiv ) - return element + newElement.appendChild( internalDiv ) + + if (properties.insertMode == "append") + element.appendChild(newElement) + if (properties.insertMode == "before") + element.parentNode.insertBefore(newElement, element) + if (properties.insertMode == "after") + element.parentNode.insertBefore(newElement, element.nextSibling) + return newElement TBError = (error) -> console.log("Error: ", error) @@ -76,8 +73,7 @@ TBUpdateObjects = ()-> console.log("JS: Object updated") streamId = e.dataset.streamid console.log("JS sessionId: " + streamId ) - id = e.id - position = getPosition(id) + position = getPosition(e) Cordova.exec(TBSuccess, TBError, OTPlugin, "updateView", [streamId, position.top, position.left, position.width, position.height, TBGetZIndex(e), ratios.widthRatio, ratios.heightRatio] ) return TBGenerateDomHelper = -> @@ -123,3 +119,11 @@ OTReplacePublisher = ()-> pdebug = (msg, data) -> console.log "JS Lib: #{msg} - ", data + +OTOnScrollEvent = (e) -> + target = e.target; + videos = target.querySelectorAll('[data-streamid]') + if(videos) + for video in videos + position = getPosition(video) + Cordova.exec(TBSuccess, TBError, OTPlugin, "updateCamera", [video.getAttribute('data-streamid'), position.top, position.left, position.width, position.height] ) diff --git a/src/js/OTPublisher.coffee b/src/js/OTPublisher.coffee index 36a028a6..53be23de 100644 --- a/src/js/OTPublisher.coffee +++ b/src/js/OTPublisher.coffee @@ -18,12 +18,12 @@ class TBPublisher constructor: (one, two) -> @sanitizeInputs(one, two) pdebug "creating publisher", {} - position = getPosition(@domId) + position = getPosition(@pubElement) name="" publishAudio="true" publishVideo="true" cameraName = "front" - zIndex = TBGetZIndex(document.getElementById(@domId)) + zIndex = TBGetZIndex(@pubElement) ratios = TBGetScreenRatios() audioFallbackEnabled = "true" audioBitrate = 40000 @@ -31,6 +31,7 @@ class TBPublisher videoSource = "true" frameRate = 30 resolution = "640X480" + insertMode = "replace" if @properties? width = @properties.width ? position.width height = @properties.height ? position.height @@ -52,12 +53,12 @@ class TBPublisher audioSource="false" if(@properties.videoSource? || @properties.videoSource==false) videoSource="false" + insertMode = @properties.insertMode ? insertMode if (not width?) or width == 0 or (not height?) or height==0 width = DefaultWidth height = DefaultHeight - @pubElement = document.getElementById(@domId) - replaceWithVideoStream(@domId, PublisherStreamId, {width:width, height:height}) - position = getPosition(@domId) + replaceWithVideoStream(@pubElement, PublisherStreamId, {width:width, height:height, insertMode:insertMode}) + position = getPosition(@pubElement) TBUpdateObjects() OT.getHelper().eventing(@) Cordova.exec(TBSuccess, TBError, OTPlugin, "initPublisher", [name, position.top, position.left, width, height, zIndex, publishAudio, publishVideo, cameraName, ratios.widthRatio, ratios.heightRatio, audioFallbackEnabled, audioBitrate, audioSource, videoSource, frameRate, resolution] ) @@ -116,24 +117,38 @@ class TBPublisher sanitizeInputs: (one, two) -> if( two? ) # all 2 optional properties present: domId, properties - @domId = one + if one instanceof Element + @pubElement = one + @domId = @pubElement.id + else + @domId = one + @pubElement = document.getElementById(one) @properties = two else if( one? ) # only 1 property is present domId || properties - if( typeof(one) == "object" ) + if one instanceof Element + @pubElement = one + @domId = @pubElement.id + else if( typeof(one) == "object" ) @properties = one else @domId = one + @pubElement = document.getElementById(one) @properties = if( @properties and typeof( @properties == "object" )) then @properties else {} + # if domId does NOT exists and an element is provided, create a unique domId + if (!@domId and @pubElement) + @domId = "PubSub" + Date.now(); + @pubElement.setAttribute('id', @domId) # if domId exists but properties width or height is not specified, set properties - if( @domId and document.getElementById( @domId ) ) + if( @domId and @pubElement ) if !@properties.width or !@properties.height console.log "domId exists but properties width or height is not specified" - position = getPosition( @domId ) + position = getPosition( @pubElement ) console.log " width: #{position.width} and height: #{position.height} for domId #{@domId}, and top: #{position.top}, left: #{position.left}" if position.width > 0 and position.height > 0 @properties.width = position.width @properties.height = position.height else @domId = TBGenerateDomHelper() + @pubElement = document.getElementById(@domId) return @ \ No newline at end of file diff --git a/src/js/OTSession.coffee b/src/js/OTSession.coffee index 85007b5a..437bcdb1 100644 --- a/src/js/OTSession.coffee +++ b/src/js/OTSession.coffee @@ -36,12 +36,12 @@ class TBSession return @ getSubscribersForStream: (stream) -> return @ - publish: (divName, properties) => + publish: (divObject, properties) => if( @alreadyPublishing ) pdebug("Session is already publishing", {}) return @alreadyPublishing = true - @publisher = new TBPublisher(divName, properties) + @publisher = new TBPublisher(divObject, properties) @publish( @publisher ) publish: () => if( @alreadyPublishing ) @@ -72,16 +72,16 @@ class TBSession return subscriber if( three? ) # stream, domId, properties || stream, domId, completionHandler || stream, properties, completionHandler - if( (typeof(two) == "string" || two.nodeType == 1) && typeof(three) == "object" ) + if( (typeof(two) == "string" || two.nodeType == 1 || two instanceof Element) && typeof(three) == "object" ) console.log("stream, domId, props") subscriber = new TBSubscriber(one, two, three) return subscriber - if( (typeof(two) == "string" || two.nodeType == 1) && typeof(three) == "function" ) + if( (typeof(two) == "string" || two.nodeType == 1 || two instanceof Element) && typeof(three) == "function" ) console.log("stream, domId, completionHandler") @subscriberCallbacks[one.streamId]=three - subscriber = new TBSubscriber(one, domId, {}) + subscriber = new TBSubscriber(one, two, {}) return subscriber - if( typeof(two) == "object" && typeof(three) == "function" ) + if(typeof(two) == "object" && typeof(three) == "function" ) console.log("stream, props, completionHandler") @subscriberCallbacks[one.streamId] = three domId = TBGenerateDomHelper() @@ -89,7 +89,7 @@ class TBSession return subscriber if( two? ) # stream, domId || stream, properties || stream,completionHandler - if( (typeof(two) == "string" || two.nodeType == 1) ) + if( (typeof(two) == "string" || two.nodeType == 1 || two instanceof Element) ) subscriber = new TBSubscriber(one, two, {}) return subscriber if( typeof(two) == "object" ) @@ -108,7 +108,7 @@ class TBSession unpublish:() -> @alreadyPublishing = false console.log("JS: Unpublish") - element = document.getElementById( @publisher.domId ) + element = @publisher.pubElement if(element) @resetElement(element) TBUpdateObjects() @@ -140,14 +140,18 @@ class TBSession @resetElement(e) objects = document.getElementsByClassName('OT_root') resetElement: (element) => - attributes = ['style', 'data-streamid', 'class'] - elementChildren = element.childNodes - element.removeAttribute attribute for attribute in attributes - for childElement in elementChildren - childClass = childElement.getAttribute 'class' - if childClass == 'OT_video-container' - element.removeChild childElement - break + insertMode = element.getAttribute('data-insertMode') + if (insertMode == "replace") + attributes = ['style', 'data-streamid', 'class', 'data-insertMode'] + elementChildren = element.childNodes + element.removeAttribute attribute for attribute in attributes + for childElement in elementChildren + childClass = childElement.getAttribute 'class' + if childClass == 'OT_video-container' + element.removeChild childElement + break + else + element.parentNode.removeChild(element) return # event listeners @@ -169,6 +173,7 @@ class TBSession delete( @connections[ connection.connectionId] ) return @ sessionConnected: (event) => + document.addEventListener('scroll', OTOnScrollEvent, true); pdebug "sessionConnectedHandler", event @trigger("sessionConnected") @connection = new TBConnection( event.connection ) @@ -176,6 +181,7 @@ class TBSession event = null return @ sessionDisconnected: (event) => + document.removeEventListener('scroll', OTOnScrollEvent); pdebug "sessionDisconnected event", event @alreadyPublishing = false sessionDisconnectedEvent = new TBEvent( { reason: event.reason } ) diff --git a/src/js/OTSubscriber.coffee b/src/js/OTSubscriber.coffee index c774db08..98449b59 100644 --- a/src/js/OTSubscriber.coffee +++ b/src/js/OTSubscriber.coffee @@ -33,20 +33,25 @@ class TBSubscriber subscribeToVideo: (value) -> return @ - constructor: (stream, divName, properties) -> - element = document.getElementById(divName) - @id = divName - @element = element + constructor: (stream, divObject, properties) -> + if divObject instanceof Element + @element = divObject + @id = @element.id + else + @id = divObject + @element = document.getElementById(divObject) + pdebug "creating subscriber", properties @streamId = stream.streamId if(properties? && properties.width=="100%" && properties.height == "100%") - element.style.width="100%" - element.style.height="100%" + @element.style.width="100%" + @element.style.height="100%" properties.width = "" properties.height = "" - divPosition = getPosition( divName ) + divPosition = getPosition(@element) subscribeToVideo="true" - zIndex = TBGetZIndex(element) + zIndex = TBGetZIndex(@element) + insertMode = "replace" if(properties?) width = properties.width || divPosition.width height = properties.height || divPosition.height @@ -57,11 +62,12 @@ class TBSubscriber subscribeToVideo="false" if(properties.subscribeToAudio? and properties.subscribeToAudio == false) subscribeToAudio="false" + insertMode = properties.insertMode ? insertMode if (not width?) or width == 0 or (not height?) or height==0 width = DefaultWidth height = DefaultHeight - obj = replaceWithVideoStream(divName, stream.streamId, {width:width, height:height}) - position = getPosition(obj.id) + obj = replaceWithVideoStream(@element, stream.streamId, {width:width, height:height, insertMode:insertMode}) + position = getPosition(@element) ratios = TBGetScreenRatios() pdebug "final subscriber position", position Cordova.exec(TBSuccess, TBError, OTPlugin, "subscribe", [stream.streamId, position.top, position.left, width, height, zIndex, subscribeToAudio, subscribeToVideo, ratios.widthRatio, ratios.heightRatio] ) diff --git a/www/opentok.js b/www/opentok.js index 1a8d948d..50c61214 100644 --- a/www/opentok.js +++ b/www/opentok.js @@ -58,6 +58,10 @@ window.addEventListener("orientationchange", (function() { }), 1000); }), false); +document.addEventListener("ondeviceready", (function() { + return Cordova.exec(TBSuccess, TBError, OTPlugin, "hasStatusBarPlugin", [window.hasOwnProperty("StatusBar")]); +}), false); + var TBConnection; TBConnection = (function() { @@ -158,44 +162,33 @@ TBEvent = (function() { })(); -var OTPublisherError, OTReplacePublisher, TBError, TBGenerateDomHelper, TBGetScreenRatios, TBGetZIndex, TBSuccess, TBUpdateObjects, getPosition, pdebug, replaceWithVideoStream, streamElements; +var OTOnScrollEvent, OTPublisherError, OTReplacePublisher, TBError, TBGenerateDomHelper, TBGetScreenRatios, TBGetZIndex, TBSuccess, TBUpdateObjects, getPosition, pdebug, replaceWithVideoStream, streamElements; streamElements = {}; -getPosition = function(divName) { - var computedStyle, curleft, curtop, height, pubDiv, width; - pubDiv = document.getElementById(divName); +getPosition = function(pubDiv) { if (!pubDiv) { return {}; } - computedStyle = window.getComputedStyle ? getComputedStyle(pubDiv, null) : {}; - width = pubDiv.offsetWidth; - height = pubDiv.offsetHeight; - curtop = pubDiv.offsetTop; - curleft = pubDiv.offsetLeft; - while ((pubDiv = pubDiv.offsetParent)) { - curleft += pubDiv.offsetLeft; - curtop += pubDiv.offsetTop; - } - return { - top: curtop, - left: curleft, - width: width, - height: height - }; + return pubDiv.getBoundingClientRect(); }; -replaceWithVideoStream = function(divName, streamId, properties) { - var element, internalDiv, typeClass, videoElement; +replaceWithVideoStream = function(element, streamId, properties) { + var internalDiv, newElement, typeClass, videoElement; typeClass = streamId === PublisherStreamId ? PublisherTypeClass : SubscriberTypeClass; - element = document.getElementById(divName); - element.setAttribute("class", "OT_root " + typeClass); - element.setAttribute("data-streamid", streamId); - element.style.width = properties.width + "px"; - element.style.height = properties.height + "px"; - element.style.overflow = "hidden"; - element.style['background-color'] = "#000000"; - streamElements[streamId] = element; + if (properties.insertMode === "replace") { + newElement = element; + } else { + newElement = document.createElement("div"); + } + newElement.setAttribute("class", "OT_root " + typeClass); + newElement.setAttribute("data-streamid", streamId); + newElement.setAttribute("data-insertMode", properties.insertMode); + newElement.style.width = properties.width + "px"; + newElement.style.height = properties.height + "px"; + newElement.style.overflow = "hidden"; + newElement.style['background-color'] = "#000000"; + streamElements[streamId] = newElement; internalDiv = document.createElement("div"); internalDiv.setAttribute("class", VideoContainerClass); internalDiv.style.width = "100%"; @@ -206,8 +199,17 @@ replaceWithVideoStream = function(divName, streamId, properties) { videoElement.style.width = "100%"; videoElement.style.height = "100%"; internalDiv.appendChild(videoElement); - element.appendChild(internalDiv); - return element; + newElement.appendChild(internalDiv); + if (properties.insertMode === "append") { + element.appendChild(newElement); + } + if (properties.insertMode === "before") { + element.parentNode.insertBefore(newElement, element); + } + if (properties.insertMode === "after") { + element.parentNode.insertBefore(newElement, element.nextSibling); + } + return newElement; }; TBError = function(error) { @@ -228,7 +230,7 @@ OTPublisherError = function(error) { }; TBUpdateObjects = function() { - var e, id, objects, position, ratios, streamId, _i, _len; + var e, objects, position, ratios, streamId, _i, _len; console.log("JS: Objects being updated in TBUpdateObjects"); objects = document.getElementsByClassName('OT_root'); ratios = TBGetScreenRatios(); @@ -237,8 +239,7 @@ TBUpdateObjects = function() { console.log("JS: Object updated"); streamId = e.dataset.streamid; console.log("JS sessionId: " + streamId); - id = e.id; - position = getPosition(id); + position = getPosition(e); Cordova.exec(TBSuccess, TBError, OTPlugin, "updateView", [streamId, position.top, position.left, position.width, position.height, TBGetZIndex(e), ratios.widthRatio, ratios.heightRatio]); } }; @@ -303,6 +304,21 @@ pdebug = function(msg, data) { return console.log("JS Lib: " + msg + " - ", data); }; +OTOnScrollEvent = function(e) { + var position, target, video, videos, _i, _len, _results; + target = e.target; + videos = target.querySelectorAll('[data-streamid]'); + if (videos) { + _results = []; + for (_i = 0, _len = videos.length; _i < _len; _i++) { + video = videos[_i]; + position = getPosition(video); + _results.push(Cordova.exec(TBSuccess, TBError, OTPlugin, "updateCamera", [video.getAttribute('data-streamid'), position.top, position.left, position.width, position.height])); + } + return _results; + } +}; + var TBPublisher, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -313,15 +329,15 @@ TBPublisher = (function() { this.streamCreated = __bind(this.streamCreated, this); this.eventReceived = __bind(this.eventReceived, this); this.setSession = __bind(this.setSession, this); - var audioBitrate, audioFallbackEnabled, audioSource, cameraName, frameRate, height, name, position, publishAudio, publishVideo, ratios, resolution, videoSource, width, zIndex, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9; + var audioBitrate, audioFallbackEnabled, audioSource, cameraName, frameRate, height, insertMode, name, position, publishAudio, publishVideo, ratios, resolution, videoSource, width, zIndex, _ref, _ref1, _ref10, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9; this.sanitizeInputs(one, two); pdebug("creating publisher", {}); - position = getPosition(this.domId); + position = getPosition(this.pubElement); name = ""; publishAudio = "true"; publishVideo = "true"; cameraName = "front"; - zIndex = TBGetZIndex(document.getElementById(this.domId)); + zIndex = TBGetZIndex(this.pubElement); ratios = TBGetScreenRatios(); audioFallbackEnabled = "true"; audioBitrate = 40000; @@ -329,6 +345,7 @@ TBPublisher = (function() { videoSource = "true"; frameRate = 30; resolution = "640X480"; + insertMode = "replace"; if (this.properties != null) { width = (_ref = this.properties.width) != null ? _ref : position.width; height = (_ref1 = this.properties.height) != null ? _ref1 : position.height; @@ -355,17 +372,18 @@ TBPublisher = (function() { if ((this.properties.videoSource != null) || this.properties.videoSource === false) { videoSource = "false"; } + insertMode = (_ref10 = this.properties.insertMode) != null ? _ref10 : insertMode; } if ((width == null) || width === 0 || (height == null) || height === 0) { width = DefaultWidth; height = DefaultHeight; } - this.pubElement = document.getElementById(this.domId); - replaceWithVideoStream(this.domId, PublisherStreamId, { + replaceWithVideoStream(this.pubElement, PublisherStreamId, { width: width, - height: height + height: height, + insertMode: insertMode }); - position = getPosition(this.domId); + position = getPosition(this.pubElement); TBUpdateObjects(); OT.getHelper().eventing(this); Cordova.exec(TBSuccess, TBError, OTPlugin, "initPublisher", [name, position.top, position.left, width, height, zIndex, publishAudio, publishVideo, cameraName, ratios.widthRatio, ratios.heightRatio, audioFallbackEnabled, audioBitrate, audioSource, videoSource, frameRate, resolution]); @@ -465,20 +483,34 @@ TBPublisher = (function() { TBPublisher.prototype.sanitizeInputs = function(one, two) { var position; if ((two != null)) { - this.domId = one; + if (one instanceof Element) { + this.pubElement = one; + this.domId = this.pubElement.id; + } else { + this.domId = one; + this.pubElement = document.getElementById(one); + } this.properties = two; } else if ((one != null)) { - if (typeof one === "object") { + if (one instanceof Element) { + this.pubElement = one; + this.domId = this.pubElement.id; + } else if (typeof one === "object") { this.properties = one; } else { this.domId = one; + this.pubElement = document.getElementById(one); } } this.properties = this.properties && typeof (this.properties === "object") ? this.properties : {}; - if (this.domId && document.getElementById(this.domId)) { + if (!this.domId && this.pubElement) { + this.domId = "PubSub" + Date.now(); + this.pubElement.setAttribute('id', this.domId); + } + if (this.domId && this.pubElement) { if (!this.properties.width || !this.properties.height) { console.log("domId exists but properties width or height is not specified"); - position = getPosition(this.domId); + position = getPosition(this.pubElement); console.log(" width: " + position.width + " and height: " + position.height + " for domId " + this.domId + ", and top: " + position.top + ", left: " + position.left); if (position.width > 0 && position.height > 0) { this.properties.width = position.width; @@ -487,6 +519,7 @@ TBPublisher = (function() { } } else { this.domId = TBGenerateDomHelper(); + this.pubElement = document.getElementById(this.domId); } return this; }; @@ -532,13 +565,13 @@ TBSession = (function() { return this; }; - TBSession.prototype.publish = function(divName, properties) { + TBSession.prototype.publish = function(divObject, properties) { if (this.alreadyPublishing) { pdebug("Session is already publishing", {}); return; } this.alreadyPublishing = true; - this.publisher = new TBPublisher(divName, properties); + this.publisher = new TBPublisher(divObject, properties); return this.publish(this.publisher); }; @@ -577,15 +610,15 @@ TBSession = (function() { return subscriber; } if ((three != null)) { - if ((typeof two === "string" || two.nodeType === 1) && typeof three === "object") { + if ((typeof two === "string" || two.nodeType === 1 || two instanceof Element) && typeof three === "object") { console.log("stream, domId, props"); subscriber = new TBSubscriber(one, two, three); return subscriber; } - if ((typeof two === "string" || two.nodeType === 1) && typeof three === "function") { + if ((typeof two === "string" || two.nodeType === 1 || two instanceof Element) && typeof three === "function") { console.log("stream, domId, completionHandler"); this.subscriberCallbacks[one.streamId] = three; - subscriber = new TBSubscriber(one, domId, {}); + subscriber = new TBSubscriber(one, two, {}); return subscriber; } if (typeof two === "object" && typeof three === "function") { @@ -597,7 +630,7 @@ TBSession = (function() { } } if ((two != null)) { - if (typeof two === "string" || two.nodeType === 1) { + if (typeof two === "string" || two.nodeType === 1 || two instanceof Element) { subscriber = new TBSubscriber(one, two, {}); return subscriber; } @@ -622,7 +655,7 @@ TBSession = (function() { var element; this.alreadyPublishing = false; console.log("JS: Unpublish"); - element = document.getElementById(this.publisher.domId); + element = this.publisher.pubElement; if (element) { this.resetElement(element); TBUpdateObjects(); @@ -683,20 +716,25 @@ TBSession = (function() { }; TBSession.prototype.resetElement = function(element) { - var attribute, attributes, childClass, childElement, elementChildren, _i, _j, _len, _len1; - attributes = ['style', 'data-streamid', 'class']; - elementChildren = element.childNodes; - for (_i = 0, _len = attributes.length; _i < _len; _i++) { - attribute = attributes[_i]; - element.removeAttribute(attribute); - } - for (_j = 0, _len1 = elementChildren.length; _j < _len1; _j++) { - childElement = elementChildren[_j]; - childClass = childElement.getAttribute('class'); - if (childClass === 'OT_video-container') { - element.removeChild(childElement); - break; + var attribute, attributes, childClass, childElement, elementChildren, insertMode, _i, _j, _len, _len1; + insertMode = element.getAttribute('data-insertMode'); + if (insertMode === "replace") { + attributes = ['style', 'data-streamid', 'class', 'data-insertMode']; + elementChildren = element.childNodes; + for (_i = 0, _len = attributes.length; _i < _len; _i++) { + attribute = attributes[_i]; + element.removeAttribute(attribute); + } + for (_j = 0, _len1 = elementChildren.length; _j < _len1; _j++) { + childElement = elementChildren[_j]; + childClass = childElement.getAttribute('class'); + if (childClass === 'OT_video-container') { + element.removeChild(childElement); + break; + } } + } else { + element.parentNode.removeChild(element); } }; @@ -730,6 +768,7 @@ TBSession = (function() { }; TBSession.prototype.sessionConnected = function(event) { + document.addEventListener('scroll', OTOnScrollEvent, true); pdebug("sessionConnectedHandler", event); this.trigger("sessionConnected"); this.connection = new TBConnection(event.connection); @@ -740,6 +779,7 @@ TBSession = (function() { TBSession.prototype.sessionDisconnected = function(event) { var sessionDisconnectedEvent; + document.removeEventListener('scroll', OTOnScrollEvent); pdebug("sessionDisconnected event", event); this.alreadyPublishing = false; sessionDisconnectedEvent = new TBEvent({ @@ -883,22 +923,27 @@ TBSubscriber = (function() { return this; }; - function TBSubscriber(stream, divName, properties) { - var divPosition, element, height, name, obj, position, ratios, subscribeToAudio, subscribeToVideo, width, zIndex, _ref; - element = document.getElementById(divName); - this.id = divName; - this.element = element; + function TBSubscriber(stream, divObject, properties) { + var divPosition, height, insertMode, name, obj, position, ratios, subscribeToAudio, subscribeToVideo, width, zIndex, _ref, _ref1; + if (divObject instanceof Element) { + this.element = divObject; + this.id = this.element.id; + } else { + this.id = divObject; + this.element = document.getElementById(divObject); + } pdebug("creating subscriber", properties); this.streamId = stream.streamId; if ((properties != null) && properties.width === "100%" && properties.height === "100%") { - element.style.width = "100%"; - element.style.height = "100%"; + this.element.style.width = "100%"; + this.element.style.height = "100%"; properties.width = ""; properties.height = ""; } - divPosition = getPosition(divName); + divPosition = getPosition(this.element); subscribeToVideo = "true"; - zIndex = TBGetZIndex(element); + zIndex = TBGetZIndex(this.element); + insertMode = "replace"; if ((properties != null)) { width = properties.width || divPosition.width; height = properties.height || divPosition.height; @@ -911,16 +956,18 @@ TBSubscriber = (function() { if ((properties.subscribeToAudio != null) && properties.subscribeToAudio === false) { subscribeToAudio = "false"; } + insertMode = (_ref1 = properties.insertMode) != null ? _ref1 : insertMode; } if ((width == null) || width === 0 || (height == null) || height === 0) { width = DefaultWidth; height = DefaultHeight; } - obj = replaceWithVideoStream(divName, stream.streamId, { + obj = replaceWithVideoStream(this.element, stream.streamId, { width: width, - height: height + height: height, + insertMode: insertMode }); - position = getPosition(obj.id); + position = getPosition(this.element); ratios = TBGetScreenRatios(); pdebug("final subscriber position", position); Cordova.exec(TBSuccess, TBError, OTPlugin, "subscribe", [stream.streamId, position.top, position.left, width, height, zIndex, subscribeToAudio, subscribeToVideo, ratios.widthRatio, ratios.heightRatio]);