Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

FollowMe.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /*
  2. * Copyright @ 2015 Atlassian Pty Ltd
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import UIEvents from '../service/UI/UIEvents';
  17. import VideoLayout from './UI/videolayout/VideoLayout';
  18. import FilmStrip from './UI/videolayout/FilmStrip';
  19. /**
  20. * The (name of the) command which transports the state (represented by
  21. * {State} for the local state at the time of this writing) of a {FollowMe}
  22. * (instance) between participants.
  23. */
  24. const _COMMAND = "follow-me";
  25. /**
  26. * Represents the set of {FollowMe}-related states (properties and their
  27. * respective values) which are to be followed by a participant. {FollowMe}
  28. * will send {_COMMAND} whenever a property of {State} changes (if the local
  29. * participant is in her right to issue such a command, of course).
  30. */
  31. class State {
  32. /**
  33. * Initializes a new {State} instance.
  34. *
  35. * @param propertyChangeCallback {Function} which is to be called when a
  36. * property of the new instance has its value changed from an old value
  37. * into a (different) new value. The function is supplied with the name of
  38. * the property, the old value of the property before the change, and the
  39. * new value of the property after the change.
  40. */
  41. constructor (propertyChangeCallback) {
  42. this._propertyChangeCallback = propertyChangeCallback;
  43. }
  44. get filmStripVisible () { return this._filmStripVisible; }
  45. set filmStripVisible (b) {
  46. var oldValue = this._filmStripVisible;
  47. if (oldValue !== b) {
  48. this._filmStripVisible = b;
  49. this._firePropertyChange('filmStripVisible', oldValue, b);
  50. }
  51. }
  52. get nextOnStage() { return this._nextOnStage; }
  53. set nextOnStage(id) {
  54. var oldValue = this._nextOnStage;
  55. if (oldValue !== id) {
  56. this._nextOnStage = id;
  57. this._firePropertyChange('nextOnStage', oldValue, id);
  58. }
  59. }
  60. get sharedDocumentVisible () { return this._sharedDocumentVisible; }
  61. set sharedDocumentVisible (b) {
  62. var oldValue = this._sharedDocumentVisible;
  63. if (oldValue !== b) {
  64. this._sharedDocumentVisible = b;
  65. this._firePropertyChange('sharedDocumentVisible', oldValue, b);
  66. }
  67. }
  68. /**
  69. * Invokes {_propertyChangeCallback} to notify it that {property} had its
  70. * value changed from {oldValue} to {newValue}.
  71. *
  72. * @param property the name of the property which had its value changed
  73. * from {oldValue} to {newValue}
  74. * @param oldValue the value of {property} before the change
  75. * @param newValue the value of {property} after the change
  76. */
  77. _firePropertyChange (property, oldValue, newValue) {
  78. var propertyChangeCallback = this._propertyChangeCallback;
  79. if (propertyChangeCallback)
  80. propertyChangeCallback(property, oldValue, newValue);
  81. }
  82. }
  83. /**
  84. * Represents the "Follow Me" feature which enables a moderator to
  85. * (partially) control the user experience/interface (e.g. film strip
  86. * visibility) of (other) non-moderator particiapnts.
  87. *
  88. * @author Lyubomir Marinov
  89. */
  90. class FollowMe {
  91. /**
  92. * Initializes a new {FollowMe} instance.
  93. *
  94. * @param conference the {conference} which is to transport
  95. * {FollowMe}-related information between participants
  96. * @param UI the {UI} which is the source (model/state) to be sent to
  97. * remote participants if the local participant is the moderator or the
  98. * destination (model/state) to receive from the remote moderator if the
  99. * local participant is not the moderator
  100. */
  101. constructor (conference, UI) {
  102. this._conference = conference;
  103. this._UI = UI;
  104. // The states of the local participant which are to be followed (by the
  105. // remote participants when the local participant is in her right to
  106. // issue such commands).
  107. this._local = new State(this._localPropertyChange.bind(this));
  108. // Listen to "Follow Me" commands. I'm not sure whether a moderator can
  109. // (in lib-jitsi-meet and/or Meet) become a non-moderator. If that's
  110. // possible, then it may be easiest to always listen to commands. The
  111. // listener will validate received commands before acting on them.
  112. conference.commands.addCommandListener(
  113. _COMMAND,
  114. this._onFollowMeCommand.bind(this));
  115. }
  116. /**
  117. * Adds listeners for the UI states of the local participant which are
  118. * to be followed (by the remote participants). A non-moderator (very
  119. * likely) can become a moderator so it may be easiest to always track
  120. * the states of interest.
  121. * @private
  122. */
  123. _addFollowMeListeners () {
  124. this.filmStripEventHandler = this._filmStripToggled.bind(this);
  125. this._UI.addListener(UIEvents.TOGGLED_FILM_STRIP,
  126. this.filmStripEventHandler);
  127. var self = this;
  128. this.pinnedEndpointEventHandler = function (smallVideo, isPinned) {
  129. self._nextOnStage(smallVideo, isPinned);
  130. };
  131. this._UI.addListener(UIEvents.PINNED_ENDPOINT,
  132. this.pinnedEndpointEventHandler);
  133. this.sharedDocEventHandler = this._sharedDocumentToggled.bind(this);
  134. this._UI.addListener( UIEvents.TOGGLED_SHARED_DOCUMENT,
  135. this.sharedDocEventHandler);
  136. }
  137. /**
  138. * Removes all follow me listeners.
  139. * @private
  140. */
  141. _removeFollowMeListeners () {
  142. this._UI.removeListener(UIEvents.TOGGLED_FILM_STRIP,
  143. this.filmStripEventHandler);
  144. this._UI.removeListener(UIEvents.TOGGLED_SHARED_DOCUMENT,
  145. this.sharedDocEventHandler);
  146. this._UI.removeListener(UIEvents.PINNED_ENDPOINT,
  147. this.pinnedEndpointEventHandler);
  148. }
  149. /**
  150. * Enables or disabled the follow me functionality
  151. *
  152. * @param enable {true} to enable the follow me functionality, {false} -
  153. * to disable it
  154. */
  155. enableFollowMe (enable) {
  156. this.isEnabled = enable;
  157. if (this.isEnabled)
  158. this._addFollowMeListeners();
  159. else
  160. this._removeFollowMeListeners();
  161. }
  162. /**
  163. * Notifies this instance that the (visibility of the) film strip was
  164. * toggled (in the user interface of the local participant).
  165. *
  166. * @param filmStripVisible {Boolean} {true} if the film strip was shown (as
  167. * a result of the toggle) or {false} if the film strip was hidden
  168. */
  169. _filmStripToggled (filmStripVisible) {
  170. this._local.filmStripVisible = filmStripVisible;
  171. }
  172. /**
  173. * Notifies this instance that the (visibility of the) shared document was
  174. * toggled (in the user interface of the local participant).
  175. *
  176. * @param sharedDocumentVisible {Boolean} {true} if the shared document was
  177. * shown (as a result of the toggle) or {false} if it was hidden
  178. */
  179. _sharedDocumentToggled (sharedDocumentVisible) {
  180. this._local.sharedDocumentVisible = sharedDocumentVisible;
  181. }
  182. /**
  183. * Changes the nextOnPage property value.
  184. *
  185. * @param smallVideo the {SmallVideo} that was pinned or unpinned
  186. * @param isPinned indicates if the given {SmallVideo} was pinned or
  187. * unpinned
  188. * @private
  189. */
  190. _nextOnStage (smallVideo, isPinned) {
  191. if (!this._conference.isModerator)
  192. return;
  193. var nextOnStage = null;
  194. if(isPinned)
  195. nextOnStage = smallVideo.getId();
  196. this._local.nextOnStage = nextOnStage;
  197. }
  198. /**
  199. * Sends the follow-me command, when a local property change occurs.
  200. *
  201. * @param property the property name
  202. * @param oldValue the old value
  203. * @param newValue the new value
  204. * @private
  205. */
  206. _localPropertyChange (property, oldValue, newValue) {
  207. // Only a moderator is allowed to send commands.
  208. var conference = this._conference;
  209. if (!conference.isModerator)
  210. return;
  211. var commands = conference.commands;
  212. // XXX The "Follow Me" command represents a snapshot of all states
  213. // which are to be followed so don't forget to removeCommand before
  214. // sendCommand!
  215. commands.removeCommand(_COMMAND);
  216. var self = this;
  217. commands.sendCommandOnce(
  218. _COMMAND,
  219. {
  220. attributes: {
  221. filmStripVisible: self._local.filmStripVisible,
  222. nextOnStage: self._local.nextOnStage,
  223. sharedDocumentVisible: self._local.sharedDocumentVisible
  224. }
  225. });
  226. }
  227. /**
  228. * Notifies this instance about a &qout;Follow Me&qout; command (delivered
  229. * by the Command(s) API of {this._conference}).
  230. *
  231. * @param attributes the attributes {Object} carried by the command
  232. * @param id the identifier of the participant who issued the command. A
  233. * notable idiosyncrasy of the Command(s) API to be mindful of here is that
  234. * the command may be issued by the local participant.
  235. */
  236. _onFollowMeCommand ({ attributes }, id) {
  237. // We require to know who issued the command because (1) only a
  238. // moderator is allowed to send commands and (2) a command MUST be
  239. // issued by a defined commander.
  240. if (typeof id === 'undefined')
  241. return;
  242. // The Command(s) API will send us our own commands and we don't want
  243. // to act upon them.
  244. if (this._conference.isLocalId(id))
  245. return;
  246. if (!this._conference.isParticipantModerator(id))
  247. {
  248. console.warn('Received follow-me command ' +
  249. 'not from moderator');
  250. return;
  251. }
  252. // Applies the received/remote command to the user experience/interface
  253. // of the local participant.
  254. this._onFilmStripVisible(attributes.filmStripVisible);
  255. this._onNextOnStage(attributes.nextOnStage);
  256. this._onSharedDocumentVisible(attributes.sharedDocumentVisible);
  257. }
  258. _onFilmStripVisible(filmStripVisible) {
  259. if (typeof filmStripVisible !== 'undefined') {
  260. // XXX The Command(s) API doesn't preserve the types (of
  261. // attributes, at least) at the time of this writing so take into
  262. // account that what originated as a Boolean may be a String on
  263. // receipt.
  264. filmStripVisible = (filmStripVisible == 'true');
  265. // FIXME The UI (module) very likely doesn't (want to) expose its
  266. // eventEmitter as a public field. I'm not sure at the time of this
  267. // writing whether calling UI.toggleFilmStrip() is acceptable (from
  268. // a design standpoint) either.
  269. if (filmStripVisible !== FilmStrip.isFilmStripVisible())
  270. this._UI.eventEmitter.emit(
  271. UIEvents.TOGGLE_FILM_STRIP,
  272. filmStripVisible);
  273. }
  274. }
  275. _onNextOnStage(id) {
  276. var clickId = null;
  277. if(typeof id !== 'undefined' && !VideoLayout.isPinned(id))
  278. clickId = id;
  279. else if (typeof id == 'undefined')
  280. clickId = VideoLayout.getPinnedId();
  281. if (clickId !== null)
  282. VideoLayout.handleVideoThumbClicked(clickId);
  283. }
  284. _onSharedDocumentVisible(sharedDocumentVisible) {
  285. if (typeof sharedDocumentVisible !== 'undefined') {
  286. // XXX The Command(s) API doesn't preserve the types (of
  287. // attributes, at least) at the time of this writing so take into
  288. // account that what originated as a Boolean may be a String on
  289. // receipt.
  290. sharedDocumentVisible = (sharedDocumentVisible == 'true');
  291. if (sharedDocumentVisible
  292. !== this._UI.getSharedDocumentManager().isVisible())
  293. this._UI.getSharedDocumentManager().toggleEtherpad();
  294. }
  295. }
  296. }
  297. export default FollowMe;