Play images and video from Synology PhotoStation server

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <?php
  2. ini_set("session.use_only_cookies", 0);
  3. set_time_limit(0);
  4. session_cache_limiter("must-revalidate");
  5. require_once('slideshow_music.inc.php');
  6. class SlideshowMusicAPI extends WebAPI {
  7. private $operationPemission = "admin";
  8. protected $idPrefix = 'slideshowmusic_';
  9. private $mimeType = array(
  10. 'mp3' => 'audio/mpeg'
  11. );
  12. function __construct() {
  13. parent::__construct(SZ_WEBAPI_API_DESCRIPTION_PATH);
  14. }
  15. protected function Process()
  16. {
  17. if (!strcasecmp($this->method, "list")) {
  18. $this->SlideshowMusicList();
  19. } elseif (!strcasecmp($this->method, "get")) {
  20. session_write_close();
  21. $this->GetMusic();
  22. } else if (!strcasecmp($this->method, "add")) {
  23. $this->AddItem();
  24. } else if (!strcasecmp($this->method, "edit")) {
  25. $this->Edit();
  26. } else if (!strcasecmp($this->method, "delete")) {
  27. $this->Delete();
  28. }
  29. }
  30. private function SlideshowMusicList()
  31. {
  32. $resp = array();
  33. $params = $this->GetParams_List();
  34. $data = SlideshowMusic::ListItem($params['offset'], $params['limit']);
  35. $items = array();
  36. foreach ($data['data'] as $k => $v) {
  37. $v['id'] = $this->EncodeItemId($k);
  38. $items[] = $v;
  39. }
  40. $resp['items'] = $items;
  41. $resp['total'] = $data['total'];
  42. $offset = (0 > (int)$params['limit']) ? $resp['total'] : $params['offset'] + $params['limit'];
  43. $resp['offset'] = ($offset > $resp['total']) ? $resp['total'] : $offset;
  44. $this->SetResponse($resp);
  45. }
  46. private function GetMusic()
  47. {
  48. $id = $this->DecodeItemId($_REQUEST['id']);
  49. if (false === $id) {
  50. $this->SetError(WEBAPI_ERR_BAD_REQUEST);
  51. goto End;
  52. }
  53. $path = SlideshowMusic::GetMusicPath($id);
  54. if (!$path) {
  55. $this->SetError(PHOTOSTATION_SLIDESHOWMUSIC_NO_FILE);
  56. goto End;
  57. }
  58. @header("Content-Type: audio/mpeg");
  59. @header("Content-Transfer-Encoding: binary");
  60. $this->GetRangeDownload($path);
  61. exit;
  62. End:
  63. return;
  64. }
  65. private function GetRangeDownload($file)
  66. {
  67. $fp = @fopen($file, 'rb');
  68. if (false == $fp) {
  69. return false;
  70. }
  71. $size = filesize($file); // File size
  72. $length = $size; // Content length
  73. $start = 0; // Start byte
  74. $end = $size - 1; // End byte
  75. // Now that we've gotten so far without errors we send the accept range header
  76. // At the moment we only support single ranges.
  77. // Multiple ranges requires some more work to ensure it works correctly
  78. // and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
  79. //
  80. // Multirange support annouces itself with:
  81. // header('Accept-Ranges: bytes');
  82. //
  83. // Multirange content must be sent with multipart/byteranges mediatype,
  84. // (mediatype = mimetype)
  85. // as well as a boundry header to indicate the various chunks of data.
  86. //
  87. //header("Accept-Ranges: 0-$length");
  88. header('Accept-Ranges: bytes');
  89. // multipart/byteranges
  90. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
  91. if (isset($_SERVER['HTTP_RANGE'])) {
  92. $c_start = $start;
  93. $c_end = $end;
  94. // Extract the range string
  95. list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
  96. // Make sure the client hasn't sent us a multibyte range
  97. if (strpos($range, ',') !== false) {
  98. // (?) Shoud this be issued here, or should the first
  99. // range be used? Or should the header be ignored and
  100. // we output the whole content?
  101. header('HTTP/1.1 416 Requested Range Not Satisfiable');
  102. header("Content-Range: bytes $start-$end/$size");
  103. // (?) Echo some info to the client?
  104. exit;
  105. }
  106. // If the range starts with an '-' we start from the beginning
  107. // If not, we forward the file pointer
  108. // And make sure to get the end byte if spesified
  109. if ($range{0} == '-') {
  110. // The n-number of the last bytes is requested
  111. $c_start = $size - substr($range, 1);
  112. } else {
  113. $range = explode('-', $range);
  114. $c_start = $range[0];
  115. $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
  116. }
  117. // Check the range and make sure it's treated according to the specs.
  118. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  119. //
  120. // End bytes can not be larger than $end.
  121. $c_end = ($c_end > $end) ? $end : $c_end;
  122. // Validate the requested range and return an error if it's not correct.
  123. if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
  124. header('HTTP/1.1 416 Requested Range Not Satisfiable');
  125. header("Content-Range: bytes $start-$end/$size");
  126. // (?) Echo some info to the client?
  127. exit;
  128. }
  129. $start = $c_start;
  130. $end = $c_end;
  131. $length = $end - $start + 1; // Calculate new content length
  132. header('HTTP/1.1 206 Partial Content');
  133. }
  134. // Notify the client the byte range we'll be outputting
  135. header("Content-Range: bytes $start-$end/$size");
  136. header("Content-Length: $length");
  137. // Start buffered download
  138. $this->OutputFileWithRange($fp, $start, $end);
  139. fclose($fp);
  140. }
  141. // this function output contain $end
  142. private function OutputFileWithRange($fp, $start, $end)
  143. {
  144. fseek($fp, $start);
  145. $buffer = 1024 * 8;
  146. while (!feof($fp) && ($end == -1 || ($p = ftell($fp)) <= $end)) {
  147. if ($p + $buffer > $end && $end != -1) {
  148. // In case we're only outputtin a chunk, make sure we don't
  149. // read past the length
  150. $buffer = $end - $p + 1;
  151. }
  152. echo fread($fp, $buffer);
  153. flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
  154. }
  155. }
  156. private function AddItem()
  157. {
  158. $filename = $_FILES['file']['name'];
  159. $title = $_REQUEST['title'];
  160. if (!csSYNOPhotoMisc::IsMusicFile($filename)) {
  161. $this->SetError(PHOTOSTATION_SLIDESHOWMUSIC_FILE_EXT_ERR);
  162. goto End;
  163. }
  164. if (!isset($_FILES['file'])) {
  165. $this->SetError(PHOTOSTATION_SLIDESHOWMUSIC_NO_FILE);
  166. goto End;
  167. }
  168. if ($_FILES['file']['error'] > 0) { // files is not uploaded successfully by form upload
  169. $this->SetError(PHOTOSTATION_SLIDESHOWMUSIC_UPLOAD_ERROR);
  170. goto End;
  171. }
  172. if (SlideshowMusic::LIMIT <= SlideshowMusic::GetCount()) {
  173. $this->SetError(array(PHOTOSTATION_SLIDESHOWMUSIC_EXCEED_LIMIT, SlideshowMusic::LIMIT));
  174. goto End;
  175. }
  176. if (!SlideshowMusic::Add($filename, $_FILES['file']['tmp_name'], $title)) {
  177. $this->SetError(PHOTOSTATION_SLIDESHOWMUSIC_SET_FAIL);
  178. goto End;
  179. }
  180. $resp = SlideshowMusic::GetLastInsertMusic();
  181. $resp['id'] = $this->EncodeItemId($resp['id']);
  182. $this->SetResponse($resp);
  183. End:
  184. return;
  185. }
  186. private function Edit()
  187. {
  188. $params = $this->GetParams_Edit();
  189. if (!$params) {
  190. $this->SetError(WEBAPI_ERR_BAD_REQUEST);
  191. goto End;
  192. }
  193. $res = SlideshowMusic::EditById($params['id'], $params['title'], $params['default']);
  194. if (!$res) {
  195. $this->SetError(PHOTOSTATION_SLIDESHOWMUSIC_SET_FAIL);
  196. }
  197. End:
  198. return;
  199. }
  200. private function Delete()
  201. {
  202. $params = $this->GetParams_Delete();
  203. if (!$params) {
  204. $this->SetError(WEBAPI_ERR_BAD_REQUEST);
  205. goto End;
  206. }
  207. $res = SlideshowMusic::DeleteById($params['id']);
  208. if (!$res) {
  209. $this->SetError(PHOTOSTATION_SLIDESHOWMUSIC_SET_FAIL);
  210. }
  211. End:
  212. return;
  213. }
  214. private function GetParams_List()
  215. {
  216. // $variable + 0 => convert to integer
  217. $params = array(
  218. 'offset' => !isset($_REQUEST['offset']) || $_REQUEST['offset'] < 0 ? 0 : $_REQUEST['offset'] + 0,
  219. 'limit' => !isset($_REQUEST['limit']) || $_REQUEST['limit'] < 0 ? NULL : $_REQUEST['limit'] + 0,
  220. );
  221. return $params;
  222. }
  223. private function GetParams_Edit()
  224. {
  225. if (!isset($_REQUEST['id']) || !((isset($_REQUEST['title']) && '' !== $_REQUEST['title']) || isset($_REQUEST['default']))) {
  226. return false;
  227. }
  228. $id = $this->DecodeItemId($_REQUEST['id']);
  229. if (false === $id) {
  230. return false;
  231. }
  232. $params = array(
  233. 'id' => $id,
  234. 'title' => $_REQUEST['title'],
  235. 'default' => isset($_REQUEST['default']) ? ("true" === $_REQUEST['default'] ? true : false) : NULL
  236. );
  237. return $params;
  238. }
  239. private function GetParams_Delete()
  240. {
  241. if (!isset($_REQUEST['id'])){
  242. return false;
  243. }
  244. $ids = explode(',', $_REQUEST['id']);
  245. $validIds= array();
  246. foreach($ids as $id) {
  247. $decodeId = $this->DecodeItemId($id);
  248. if(false === $decodeId) {
  249. continue;
  250. }
  251. $validIds[] = $decodeId;
  252. }
  253. if (!count($validIds)) {
  254. return false;
  255. }
  256. $params = array(
  257. 'id' => $validIds
  258. );
  259. return $params;
  260. }
  261. protected function CheckPermission()
  262. {
  263. $res = true;
  264. $check = array(
  265. "add" => $this->operationPemission,
  266. "edit" => $this->operationPemission,
  267. "delete" => $this->operationPemission
  268. );
  269. if (!array_key_exists($this->method, $check)) {
  270. goto End;
  271. }
  272. $funcName = "check_".$check[$this->method];
  273. if (!method_exists($this, $funcName)) {
  274. $res = false;
  275. goto End;
  276. }
  277. $res = $this->$funcName();
  278. End:
  279. if (!$res) {
  280. $this->SetError(PHOTOSTATION_SLIDESHOWMUSIC_ACCESS_DENY);
  281. }
  282. return $res;
  283. }
  284. private function check_admin()
  285. {
  286. csSYNOPhotoDB::GetDBInstance()->SetSessionCache();
  287. csSYNOPhotoMisc::CheckSessionTimeOut(true);
  288. return isset($_SESSION[SYNOPHOTO_ADMIN_USER]['admin_syno_user']);
  289. }
  290. }
  291. $api = new SlideshowMusicAPI();
  292. $api->Run();
  293. ?>