PageRenderTime 28ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/class/broadcast_server.class.php

https://gitlab.com/x33n/ampache
PHP | 442 lines | 272 code | 43 blank | 127 comment | 42 complexity | 26c842e92069f7a95181d733214d5a49 MD5 | raw file
  1. <?php
  2. /* vim:set softtabstop=4 shiftwidth=4 expandtab: */
  3. /**
  4. *
  5. * LICENSE: GNU General Public License, version 2 (GPLv2)
  6. * Copyright 2001 - 2015 Ampache.org
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License v2
  10. * as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. *
  21. */
  22. use Ratchet\MessageComponentInterface;
  23. use Ratchet\ConnectionInterface;
  24. class Broadcast_Server implements MessageComponentInterface
  25. {
  26. const BROADCAST_SONG = "SONG";
  27. const BROADCAST_SONG_POSITION = "SONG_POSITION";
  28. const BROADCAST_PLAYER_PLAY = "PLAYER_PLAY";
  29. const BROADCAST_REGISTER_BROADCAST = "REGISTER_BROADCAST";
  30. const BROADCAST_REGISTER_LISTENER = "REGISTER_LISTENER";
  31. const BROADCAST_ENDED = "ENDED";
  32. const BROADCAST_INFO = "INFO";
  33. const BROADCAST_NB_LISTENERS = "NB_LISTENERS";
  34. const BROADCAST_AUTH_SID = "AUTH_SID";
  35. public $verbose;
  36. /**
  37. * @var ConnectionInterface[] $clients
  38. */
  39. protected $clients;
  40. /**
  41. * @var string[] $sids
  42. */
  43. protected $sids;
  44. /**
  45. * @var ConnectionInterface[] $listeners
  46. */
  47. protected $listeners;
  48. /**
  49. * @var Broadcast[] $broadcasters
  50. */
  51. protected $broadcasters;
  52. public function __construct()
  53. {
  54. $this->verbose = false;
  55. $this->clients = array();
  56. $this->sids = array();
  57. $this->listeners = array();
  58. $this->broadcasters = array();
  59. }
  60. /**
  61. *
  62. * @param \Ratchet\ConnectionInterface $conn
  63. */
  64. public function onOpen(ConnectionInterface $conn)
  65. {
  66. $this->clients[$conn->resourceId] = $conn;
  67. }
  68. /**
  69. *
  70. * @param \Ratchet\ConnectionInterface $from
  71. * @param string $msg
  72. */
  73. public function onMessage(ConnectionInterface $from, $msg)
  74. {
  75. $commands = explode(';', $msg);
  76. foreach ($commands as $command) {
  77. $command = trim($command);
  78. if (!empty($command)) {
  79. $cmdinfo = explode(':', $command, 2);
  80. if (count($cmdinfo) == 2) {
  81. switch ($cmdinfo[0]) {
  82. case self::BROADCAST_SONG:
  83. $this->notifySong($from, $cmdinfo[1]);
  84. break;
  85. case self::BROADCAST_SONG_POSITION:
  86. $this->notifySongPosition($from, $cmdinfo[1]);
  87. break;
  88. case self::BROADCAST_PLAYER_PLAY:
  89. $this->notifyPlayerPlay($from, $cmdinfo[1]);
  90. break;
  91. case self::BROADCAST_ENDED:
  92. $this->notifyEnded($from);
  93. break;
  94. case self::BROADCAST_REGISTER_BROADCAST:
  95. $this->registerBroadcast($from, $cmdinfo[1]);
  96. break;
  97. case self::BROADCAST_REGISTER_LISTENER:
  98. $this->registerListener($from, $cmdinfo[1]);
  99. break;
  100. case self::BROADCAST_AUTH_SID:
  101. $this->authSid($from, $cmdinfo[1]);
  102. break;
  103. default:
  104. if ($this->verbose) {
  105. echo "[" . time() ."][warning]Unknown message code." . "\r\n";
  106. }
  107. break;
  108. }
  109. } else {
  110. if ($this->verbose) {
  111. echo "[" . time() ."][error]Wrong message format (" . $command . ")." . "\r\n";
  112. }
  113. }
  114. }
  115. }
  116. }
  117. /**
  118. *
  119. * @param int $song_id
  120. * @return string
  121. */
  122. protected function getSongJS($song_id)
  123. {
  124. $media = array();
  125. $media[] = array(
  126. 'object_type' => 'song',
  127. 'object_id' => $song_id
  128. );
  129. $item = Stream_Playlist::media_to_urlarray($media);
  130. return WebPlayer::get_media_js_param($item[0]);
  131. }
  132. /**
  133. *
  134. * @param \Ratchet\ConnectionInterface $from
  135. * @param int $song_id
  136. */
  137. protected function notifySong(ConnectionInterface $from, $song_id)
  138. {
  139. if ($this->isBroadcaster($from)) {
  140. $broadcast = $this->broadcasters[$from->resourceId];
  141. $clients = $this->getListeners($broadcast);
  142. Session::extend(Stream::get_session(), 'stream');
  143. $broadcast->update_song($song_id);
  144. $this->broadcastMessage($clients, self::BROADCAST_SONG, base64_encode($this->getSongJS($song_id)));
  145. if ($this->verbose) {
  146. echo "[" . time() ."][info]Broadcast " . $broadcast->id . " now playing song " . $song_id . "." . "\r\n";
  147. }
  148. } else {
  149. debug_event('broadcast', 'Action unauthorized.', '3');
  150. }
  151. }
  152. /**
  153. *
  154. * @param \Ratchet\ConnectionInterface $from
  155. * @param int $song_position
  156. */
  157. protected function notifySongPosition(ConnectionInterface $from, $song_position)
  158. {
  159. if ($this->isBroadcaster($from)) {
  160. $broadcast = $this->broadcasters[$from->resourceId];
  161. $seekdiff = $broadcast->song_position - $song_position;
  162. if ($seekdiff > 2 || $seekdiff < -2) {
  163. $clients = $this->getListeners($broadcast);
  164. $this->broadcastMessage($clients, self::BROADCAST_SONG_POSITION, $song_position);
  165. }
  166. $broadcast->song_position = $song_position;
  167. if ($this->verbose) {
  168. echo "[" . time() ."][info]Broadcast " . $broadcast->id . " has song position to " . $song_position . "." . "\r\n";
  169. }
  170. } else {
  171. debug_event('broadcast', 'Action unauthorized.', '3');
  172. }
  173. }
  174. /**
  175. *
  176. * @param \Ratchet\ConnectionInterface $from
  177. * @param boolean $play
  178. */
  179. protected function notifyPlayerPlay(ConnectionInterface $from, $play)
  180. {
  181. if ($this->isBroadcaster($from)) {
  182. $broadcast = $this->broadcasters[$from->resourceId];
  183. $clients = $this->getListeners($broadcast);
  184. $this->broadcastMessage($clients, self::BROADCAST_PLAYER_PLAY, $play ? 'true' : 'false');
  185. if ($this->verbose) {
  186. echo "[" . time() ."][info]Broadcast " . $broadcast->id . " player state: " . $play . "." . "\r\n";
  187. }
  188. } else {
  189. debug_event('broadcast', 'Action unauthorized.', '3');
  190. }
  191. }
  192. /**
  193. *
  194. * @param \Ratchet\ConnectionInterface $from
  195. */
  196. protected function notifyEnded(ConnectionInterface $from)
  197. {
  198. if ($this->isBroadcaster($from)) {
  199. $broadcast = $this->broadcasters[$from->resourceId];
  200. $clients = $this->getListeners($broadcast);
  201. $this->broadcastMessage($clients, self::BROADCAST_ENDED);
  202. if ($this->verbose) {
  203. echo "[" . time() ."][info]Broadcast " . $broadcast->id . " ended." . "\r\n";
  204. }
  205. } else {
  206. debug_event('broadcast', 'Action unauthorized.', '3');
  207. }
  208. }
  209. /**
  210. *
  211. * @param \Ratchet\ConnectionInterface $from
  212. * @param string $broadcast_key
  213. */
  214. protected function registerBroadcast(ConnectionInterface $from, $broadcast_key)
  215. {
  216. $broadcast = Broadcast::get_broadcast($broadcast_key);
  217. if ($broadcast) {
  218. $this->broadcasters[$from->resourceId] = $broadcast;
  219. $this->listeners[$broadcast->id] = array();
  220. if ($this->verbose) {
  221. echo "[info]Broadcast " . $broadcast->id . " registered." . "\r\n";
  222. }
  223. }
  224. }
  225. /**
  226. *
  227. * @param \Ratchet\ConnectionInterface $conn
  228. */
  229. protected function unregisterBroadcast(ConnectionInterface $conn)
  230. {
  231. $broadcast = $this->broadcasters[$conn->resourceId];
  232. $clients = $this->getListeners($broadcast);
  233. $this->broadcastMessage($clients, self::BROADCAST_ENDED);
  234. $broadcast->update_state(false);
  235. unset($this->listeners[$broadcast->id]);
  236. unset($this->broadcasters[$conn->resourceId]);
  237. if ($this->verbose) {
  238. echo "[" . time() ."][info]Broadcast " . $broadcast->id . " unregistered." . "\r\n";
  239. }
  240. }
  241. /**
  242. *
  243. * @param int $broadcast_id
  244. * @return Broadcast
  245. */
  246. protected function getRunningBroadcast($broadcast_id)
  247. {
  248. $broadcast = null;
  249. foreach ($this->broadcasters as $conn_id => $br) {
  250. if ($br->id == $broadcast_id) {
  251. $broadcast = $br;
  252. break;
  253. }
  254. }
  255. return $broadcast;
  256. }
  257. /**
  258. *
  259. * @param \Ratchet\ConnectionInterface $from
  260. * @param int $broadcast_id
  261. */
  262. protected function registerListener(ConnectionInterface $from, $broadcast_id)
  263. {
  264. $broadcast = $this->getRunningBroadcast($broadcast_id);
  265. if (!$broadcast->is_private || !AmpConfig::get('require_session') || Session::exists('stream', $this->sids[$from->resourceId])) {
  266. $this->listeners[$broadcast->id][] = $from;
  267. // Send current song and song position to
  268. $this->broadcastMessage(array($from), self::BROADCAST_SONG, base64_encode($this->getSongJS($broadcast->song)));
  269. $this->broadcastMessage(array($from), self::BROADCAST_SONG_POSITION, $broadcast->song_position);
  270. $this->notifyNbListeners($broadcast);
  271. if ($this->verbose) {
  272. echo "[info]New listener on broadcast " . $broadcast->id . "." . "\r\n";
  273. }
  274. } else {
  275. debug_event('broadcast', 'Listener unauthorized.', '3');
  276. }
  277. }
  278. /**
  279. *
  280. * @param \Ratchet\ConnectionInterface $conn
  281. * @param string $sid
  282. */
  283. protected function authSid(ConnectionInterface $conn, $sid)
  284. {
  285. if (Session::exists('stream', $sid)) {
  286. $this->sids[$conn->resourceId] = $sid;
  287. } else {
  288. if ($this->verbose) {
  289. echo "Wrong listener session " . $sid . "\r\n";
  290. }
  291. }
  292. }
  293. /**
  294. *
  295. * @param \Ratchet\ConnectionInterface $conn
  296. */
  297. protected function unregisterListener(ConnectionInterface $conn)
  298. {
  299. foreach ($this->listeners as $broadcast_id => $brlisteners) {
  300. $lindex = array_search($conn, $brlisteners);
  301. if ($lindex) {
  302. unset($this->listeners[$broadcast_id][$lindex]);
  303. echo "[info]Listener leaved broadcast " . $broadcast_id . "." . "\r\n";
  304. foreach ($this->broadcasters as $broadcaster_id => $broadcast) {
  305. if ($broadcast->id == $broadcast_id) {
  306. $this->notifyNbListeners($broadcast);
  307. break;
  308. }
  309. }
  310. break;
  311. }
  312. }
  313. }
  314. /**
  315. *
  316. * @param Broadcast $broadcast
  317. */
  318. protected function notifyNbListeners(Broadcast $broadcast)
  319. {
  320. $broadcaster_id = array_search($broadcast, $this->broadcasters);
  321. if ($broadcaster_id) {
  322. $clients = $this->listeners[$broadcast->id];
  323. $clients[] = $this->clients[$broadcaster_id];
  324. $nb_listeners = count($this->listeners[$broadcast->id]);
  325. $broadcast->update_listeners($nb_listeners);
  326. $this->broadcastMessage($clients, self::BROADCAST_NB_LISTENERS, $nb_listeners);
  327. }
  328. }
  329. /**
  330. *
  331. * @param Broadcast $broadcast
  332. * @return \Ratchet\ConnectionInterface
  333. */
  334. protected function getListeners(Broadcast $broadcast)
  335. {
  336. return $this->listeners[$broadcast->id];
  337. }
  338. /**
  339. *
  340. * @param \Ratchet\ConnectionInterface $conn
  341. * @return boolean
  342. */
  343. protected function isBroadcaster(ConnectionInterface $conn)
  344. {
  345. return array_key_exists($conn->resourceId, $this->broadcasters);
  346. }
  347. /**
  348. *
  349. * @param \Ratchet\ConnectionInterface[] $clients
  350. * @param string $cmd
  351. * @param string $value
  352. */
  353. protected function broadcastMessage($clients, $cmd, $value='')
  354. {
  355. $msg = $cmd . ':' . $value . ';';
  356. foreach ($clients as $client) {
  357. $sid = $this->sids[$client->resourceId];
  358. if ($sid) {
  359. Session::extend($sid, 'stream');
  360. }
  361. $client->send($msg);
  362. }
  363. }
  364. /**
  365. *
  366. * @param \Ratchet\ConnectionInterface $conn
  367. */
  368. public function onClose(ConnectionInterface $conn)
  369. {
  370. if ($this->isBroadcaster($conn)) {
  371. $this->unregisterBroadcast($conn);
  372. } else {
  373. $this->unregisterListener($conn);
  374. }
  375. unset($this->clients[$conn->resourceId]);
  376. unset($this->sids[$conn->resourceId]);
  377. }
  378. /**
  379. *
  380. * @param \Ratchet\ConnectionInterface $conn
  381. * @param \Exception $e
  382. */
  383. public function onError(ConnectionInterface $conn, \Exception $e)
  384. {
  385. $conn->close();
  386. }
  387. /**
  388. *
  389. * @return string
  390. */
  391. public static function get_address()
  392. {
  393. $websocket_address = AmpConfig::get('websocket_address');
  394. if (empty($websocket_address)) {
  395. $websocket_address = 'ws://' . $_SERVER['HTTP_HOST'] . ':8100';
  396. }
  397. return $websocket_address . '/broadcast';
  398. }
  399. } // end of broadcast_server class