embedded.html 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. <!DOCTYPE html>
  2. <meta name="apple-mobile-web-app-capable" content="yes" />
  3. <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
  4. <html>
  5. <head>
  6. <meta charset="UTF-8">
  7. <meta name="theme-color" content="#232324">
  8. <link rel="stylesheet" href="../script/tocas/tocas.css">
  9. <script src="../script/tocas/tocas.js"></script>
  10. <script src="../script/jquery.min.js"></script>
  11. <script src="../script/ao_module.js"></script>
  12. <style>
  13. body{
  14. padding:0px !important;
  15. overflow: hidden;
  16. height:100%;
  17. }
  18. html, body { height: 100%; width: 100%; margin: 0; }
  19. .playerInterface{
  20. background-color:rgba(226, 221, 220,0.95);
  21. height:150px !important;
  22. width:100%;
  23. }
  24. #playerUI{
  25. margin-left:0px !important;
  26. margin-right:0px !important;
  27. width:100% !important;
  28. }
  29. .parkLeft{
  30. position:absolute !important;
  31. top:70px;
  32. left:5px;
  33. margin-left:-30px;
  34. }
  35. .rotateNinetyDegree{
  36. -webkit-transform: rotate(-90deg);
  37. -moz-transform: rotate(-90deg);
  38. -o-transform: rotate(-90deg);
  39. -ms-transform: rotate(-90deg);
  40. transform: rotate(-90deg);
  41. }
  42. .roundbtn{
  43. border-radius: 40px !important;
  44. box-shadow: 2px 2px 2px 2px #bfbfbf;
  45. }
  46. .playBtn{
  47. color:#4b75ff !important;
  48. }
  49. .playerControlWrapper{
  50. text-align="center";
  51. position:absolute;
  52. bottom:50px;
  53. width:100% !important;
  54. }
  55. .rightPaddedOprButtons{
  56. position:absolute;
  57. right:3px;
  58. top:3px;
  59. }
  60. .modeEnabled{
  61. background-color:#4b75ff !important;
  62. color:white !important;
  63. border: 1px solid #3d5fd1 !important;
  64. }
  65. #volControlOverlay{
  66. background-color: rgba(255,255,255,0.01);
  67. z-index:999;
  68. position:absolute;
  69. width:14px;
  70. height:120px;
  71. top:17px;
  72. left:28px;
  73. }
  74. .songlabel{
  75. padding-right:3px;
  76. position:absolute;
  77. display:inline-box;
  78. left:5px;
  79. top:5px;
  80. color:white;
  81. height:22px;
  82. text-overflow: ellipsis;
  83. overflow: hidden;
  84. width: 100%;
  85. white-space: nowrap;
  86. }
  87. .durationDisplay{
  88. position:absolute;
  89. right:0px;
  90. bottom:0px;
  91. padding-right:5px;
  92. padding-bottom:5px;
  93. margin: 0px !important;
  94. }
  95. .bottomBackground{
  96. background-color: rgba(48,48,48,0.95);
  97. height:58px;
  98. width:100%;
  99. }
  100. .sameDirFileList{
  101. width:100%;
  102. height:calc(100% - 220px) !important;
  103. background-color:rgba(52,52,52,0.9);
  104. overflow-y:auto;
  105. overflow-x:hidden;
  106. }
  107. @supports ((-webkit-backdrop-filter: blur(2em)) or (backdrop-filter: blur(2em))) {
  108. .sameDirFileList {
  109. background-color:rgba(52,52,52,0.5);
  110. -webkit-backdrop-filter: blur(2em);
  111. backdrop-filter: blur(2em);
  112. }
  113. }
  114. .listitem{
  115. border-bottom: 1px solid #404040;
  116. padding: 12px;
  117. color:white;
  118. font-size:120%;
  119. cursor:pointer;
  120. }
  121. .listitem:hover{
  122. background-color:#545454;
  123. }
  124. .listitem.selected{
  125. background-color:#5e5e5e;
  126. }
  127. </style>
  128. </head>
  129. <body>
  130. <div id="playerUI" class="playerInterface ts container">
  131. <div class="parkLeft">
  132. <div class="ts primary small progress rotateNinetyDegree" style="width:120px !important;">
  133. <div id="volControl" class="bar" style="width: 60%;background-color:#ff4b4b;"></div>
  134. </div>
  135. </div>
  136. <div id="volControlOverlay"></div>
  137. <div class="playerControlWrapper" align="center">
  138. <button class="ts huge icon button roundbtn playlistStepBtn" onClick="lastSong();"><i class="step backward icon"></i></button>
  139. <button class="ts massive icon button roundbtn playBtn" onClick="togglePlay(this);"><i class="pause icon"></i></button>
  140. <button class="ts huge icon button roundbtn playlistStepBtn" onClick="nextSong();"><i class="step forward icon"></i></button>
  141. </div>
  142. <div class="rightPaddedOprButtons">
  143. <button id="repeatModeBtn" class="ts icon button" style="margin-bottom:5px;" onClick="toggleRepeat(this);"><i class="retweet icon"></i></button><br>
  144. <button id="showListBtn" class="ts icon button" onClick="showRelatedFileList();"><i class="content icon"></i></button>
  145. <!-- <button class="ts icon button"><i class="exchange icon"></i></button> -->
  146. </div>
  147. <!-- Adding in some labels for the progress bars and song information-->
  148. <p class="rotateNinetyDegree" style="position:absolute;top:55px;left:5px;"><i class="minus icon"></i> Volume <i class="plus icon"></i></p>
  149. <p class="durationDisplay">0:00 / 0:00</p>
  150. </div>
  151. <div id="progressControl" class="ts attached small progress">
  152. <div id="playbackProgress" class="bar" style="width: 0%;background-color: #4b75ff;"></div>
  153. </div>
  154. <div class="bottomBackground">
  155. <!-- Song Title at the bottom black area-->
  156. <div id="songtitle" class="songlabel">Loading Song Information...</div>
  157. <div id="fileSize" style="position:absolute;bottom:5px;left:5px;color:white;">N/A</div>
  158. <div style="position:absolute;right:3px;bottom:3px;color:white;"><i class='leaf icon'></i>AirMusic</div>
  159. </div>
  160. <!-- The sections below are for mobile interfaces -->
  161. <div id="nearbyFilelist" class="sameDirFileList">
  162. <div class="listitem" onClick="playThis(this);">N/A</div>
  163. </div>
  164. <!-- autoplay -->
  165. <audio id="mainPlayer" style="display:none;" preload="auto" autoplay></audio>
  166. <div style="display:none;">
  167. <div id="meta_dirSongList"></div>
  168. <div id="meta_playingSong"></div>
  169. </div>
  170. <script>
  171. //Define global variables
  172. var player = $("#mainPlayer")[0];
  173. var mediaExchanging = false;
  174. var listShown = true;
  175. if (ao_module_virtualDesktop){
  176. listShown = false;
  177. }else{
  178. $("#showListBtn").hide();
  179. }
  180. player.volume = getCurrentGlobVol();
  181. //Init ao_module window events
  182. ao_module_setFixedWindowSize();
  183. //Ignore all other input files.
  184. playingFileInfo = ao_module_loadInputFiles()[0];
  185. //Get the song title and meta data from server
  186. var songInfo = [];
  187. var songList = [];
  188. ao_module_agirun("Music/functions/getMeta.js", {
  189. file: encodeURIComponent(playingFileInfo.filepath)
  190. }, function(data){
  191. songList = data;
  192. for (var i = 0; i < data.length; i++){
  193. if (data[i][0] == playingFileInfo.filename){
  194. songInfo = data[i];
  195. //Set the audio element src
  196. player.src = "../media?file=" + encodeURIComponent(data[i][1]);
  197. //Update window title
  198. ao_module_setWindowTitle(data[i][0]);
  199. }
  200. }
  201. //Load song title into the display area
  202. updateDisplayInformation(songInfo[0],songInfo[3]);
  203. //Create the playlist for all the files in the same directory
  204. createPlayList();
  205. //Initiate the current playSong in the songList
  206. updatePlayingSongSelection();
  207. })
  208. /*
  209. $.get("../Music/getMeta?file=" + encodeURIComponent(playingFileInfo.filepath),function(data){
  210. //console.log(data);
  211. songList = data;
  212. for (var i = 0; i < data.length; i++){
  213. if (data[i][0] == playingFileInfo.filename){
  214. songInfo = data[i];
  215. //Set the audio element src
  216. player.src = "../media?file=" + encodeURIComponent(data[i][1]);
  217. //Update window title
  218. ao_module_setWindowTitle(data[i][0]);
  219. }
  220. }
  221. //Load song title into the display area
  222. updateDisplayInformation(songInfo[0],songInfo[3]);
  223. //Create the playlist for all the files in the same directory
  224. createPlayList();
  225. //Initiate the current playSong in the songList
  226. updatePlayingSongSelection();
  227. });
  228. */
  229. //Get the song title from meta data
  230. //var songInfo = JSON.parse($("#meta_playingSong").text().trim());
  231. //var songList = JSON.parse($("#meta_dirSongList").text().trim());
  232. //ao_module_setWindowTitle(ao_module_codec.decodeUmFilename(songInfo[0]));
  233. //Define supporting function for trunc.
  234. String.prototype.trunc = String.prototype.trunc ||
  235. function(n){
  236. return (this.length > n) ? this.substr(0, n-1) + '&hellip;' : this;
  237. };
  238. //Setup listen events to global volume
  239. setInterval(function(){
  240. var globvol = getCurrentGlobVol();
  241. updateVolControlDisplay(globvol);
  242. player.volume = globvol;
  243. if (player.paused == true){
  244. //Check if anytime that the button UI is not in sync with the current playing status. Update it if found.
  245. $(".playBtn").html("<i class='play icon'></i>");
  246. }else{
  247. $(".playBtn").html("<i class='pause icon'></i>");
  248. }
  249. },1000);
  250. updateVolControlDisplay(getCurrentGlobVol());
  251. //Add volume bar change event listener
  252. $("#volControlOverlay").on("click",function(e){
  253. var x = e.pageX - $('#volControlOverlay').offset().left;
  254. var y = e.pageY - $('#volControlOverlay').offset().top;
  255. var height = $('#volControlOverlay').height();
  256. var newvol = Math.round(((height - y) / height)*20)/20;
  257. localStorage.setItem("global_volume",newvol);
  258. player.volume = newvol;
  259. updateVolControlDisplay(newvol);
  260. });
  261. //Also add draging bar for mouse operations
  262. player.onended = function(){
  263. if (!repeatMode){
  264. $(".playBtn").html("<i class='play icon'></i>");
  265. }
  266. }
  267. var dragging = false;
  268. $("#volControlOverlay").on("mousedown",function(){
  269. dragging = true;
  270. });
  271. $("#volControlOverlay").on("mousemove",function(e){
  272. if (dragging){
  273. var y = e.pageY - $('#volControlOverlay').offset().top;
  274. var height = $('#volControlOverlay').height();
  275. var newvol = Math.round(((height - y) / height)*100)/100;
  276. //console.log(newvol);
  277. localStorage.setItem("global_volume",newvol);
  278. player.volume = newvol;
  279. updateVolControlDisplay(newvol);
  280. }
  281. });
  282. $("#volControlOverlay").on("mouseup",function(){
  283. if (dragging){
  284. dragging = false;
  285. }
  286. });
  287. $("body").on("mouseup",function(){
  288. if (dragging){
  289. dragging = false;
  290. }
  291. });
  292. //Audio element custom UI listeners
  293. document.getElementById("progressControl").addEventListener("click",function(e){
  294. //Click on the progress control bar, update the audio playing location
  295. var x = e.pageX - $('#progressControl').offset().left;
  296. var w = $("#progressControl").width();
  297. var alength = player.duration;
  298. var targetPos = (x / w * alength);
  299. player.currentTime = targetPos;
  300. });
  301. player.addEventListener("timeupdate", function(){
  302. var currentTime = player.currentTime;
  303. var duration = player.duration;
  304. var progressPercentage = currentTime / duration * 100 + "%";
  305. $("#playbackProgress").css("width",progressPercentage);
  306. if (!mediaExchanging){
  307. updateDurationDisplay(currentTime,duration);
  308. }
  309. });
  310. //Load repeat mode from storage
  311. var repeatMode = false;
  312. repeatMode = loadStorage("repeatModeEmbedded");
  313. if (repeatMode != ""){
  314. repeatMode = (repeatMode == "true");
  315. }
  316. if (repeatMode){
  317. $("#repeatModeBtn").addClass("modeEnabled");
  318. player.loop = true;
  319. }
  320. //End of startup seuqence
  321. function updatePlayingSongSelection(){
  322. $(".listitem.selected").removeClass("selected");
  323. $(".listitem").each(function(){
  324. if ($(this).attr("filename") == songInfo[0]){
  325. $(this).addClass("selected");
  326. }
  327. });
  328. }
  329. function showRelatedFileList(){
  330. if (ao_module_virtualDesktop){
  331. if (listShown){
  332. ao_module_setWindowSize(360,240);
  333. listShown = false;
  334. }else{
  335. ao_module_setWindowSize(360,520);
  336. listShown = true;
  337. }
  338. }
  339. }
  340. function lastSong(){
  341. //Switch to the next song in list
  342. player.pause();
  343. mediaExchanging = true;
  344. $(".durationDisplay").html('<i class="clock icon"></i> Loading Media');
  345. var nextSong = -1; //There is no next song in the current directory
  346. for (var i =0; i < songList.length; i++){
  347. if (songList[i][0] == songInfo[0]){
  348. //Filename are the same
  349. if (i-1 < 0){
  350. //This is the last song in list
  351. nextSong = songList[songList.length - 1]
  352. }else{
  353. //Play previous song
  354. nextSong = songList[i-1]
  355. }
  356. }
  357. }
  358. if (nextSong == -1){
  359. //There is no more similar file with similar extensions or there are no other supported files in the same directory.
  360. player.currentTime = 0;
  361. updateDisplayInformation(ao_module_codec.decodeUmFilename(songInfo[0]),songInfo[3]);
  362. player.play();
  363. setTimeout(function(){
  364. mediaExchanging = false;
  365. },300);
  366. return;
  367. }
  368. loadSongFromSongInfo(nextSong);
  369. player.play();
  370. setTimeout(function(){
  371. mediaExchanging = false;
  372. },300);
  373. updatePlayingSongSelection();
  374. }
  375. function createPlayList(){
  376. $(".sameDirFileList").html("");
  377. var template = '<div class="listitem" filename="{rawname}" onClick="playThis(this);"><i class="file audio outline icon"></i> {songname} ({filesize})</div>';
  378. for (var i = 0; i < songList.length; i++){
  379. var box = template;
  380. box = box.split("{rawname}").join(songList[i][0]);
  381. box = box.split("{songname}").join(ao_module_codec.decodeUmFilename(songList[i][0]));
  382. box = box.split("{filesize}").join(songList[i][3]);
  383. //console.log(box);
  384. $(".sameDirFileList").append(box);
  385. }
  386. }
  387. function playThis(object){
  388. var songName = $(object).attr("filename");
  389. $(".listitem.selected").removeClass("selected");
  390. $(object).addClass("selected");
  391. for (var i =0; i < songList.length; i++){
  392. if (songList[i][0] == songName){
  393. //This is the song that the user request to play.
  394. player.pause();
  395. mediaExchanging = true;
  396. $(".durationDisplay").html('<i class="clock icon"></i> Loading Media');
  397. loadSongFromSongInfo(songList[i]);
  398. player.play();
  399. setTimeout(function(){
  400. mediaExchanging = false;
  401. },300);
  402. break;
  403. }
  404. }
  405. }
  406. function nextSong(){
  407. //Switch to the next song in list
  408. player.pause();
  409. mediaExchanging = true;
  410. $(".durationDisplay").html('<i class="clock icon"></i> Loading Media');
  411. var nextSong = -1; //There is no next song in the current directory
  412. for (var i =0; i < songList.length; i++){
  413. if (songList[i][0] == songInfo[0]){
  414. //Filename are the same
  415. if (i+1 >= songList.length){
  416. //This is the last song in list
  417. nextSong = songList[0]
  418. }else{
  419. //Play next song
  420. nextSong = songList[i+1]
  421. }
  422. }
  423. }
  424. if (nextSong == -1){
  425. //There is no more similar file with similar extensions or there are no other supported files in the same directory.
  426. player.currentTime = 0;
  427. updateDisplayInformation(ao_module_codec.decodeUmFilename(songInfo[0]), songInfo[3]);
  428. player.play();
  429. setTimeout(function(){
  430. mediaExchanging = false;
  431. },300);
  432. return;
  433. }
  434. loadSongFromSongInfo(nextSong);
  435. player.play();
  436. setTimeout(function(){
  437. mediaExchanging = false;
  438. },300);
  439. updatePlayingSongSelection();
  440. }
  441. function blobToDataURL(blob, callback) {
  442. var a = new FileReader();
  443. a.onload = function(e) {callback(e.target.result);}
  444. a.readAsDataURL(blob);
  445. }
  446. function loadSongFromSongInfo(playSongInfo){
  447. var songName = ao_module_codec.decodeUmFilename(playSongInfo[0]);
  448. var songPath = filterExternalStoragePath(playSongInfo[1]);
  449. var fileSize = playSongInfo[3];
  450. $(player).attr('src',"/media?file=" + encodeURIComponent(songPath));
  451. ao_module_setWindowTitle(songName);
  452. songInfo = playSongInfo;
  453. updateDisplayInformation(ao_module_codec.decodeUmFilename(songInfo[0]),songInfo[3]);
  454. }
  455. function updateDisplayInformation(songname, filesize){
  456. $("#songtitle").html("<i class='music icon'></i>" + songname)
  457. $("#fileSize").html("<i class='file outline icon'></i> " + filesize);
  458. }
  459. function filterExternalStoragePath(filepath){
  460. return filepath;
  461. }
  462. function updateDurationDisplay(pos,dur){
  463. pos = secondsToHMS(pos);
  464. dur = secondsToHMS(dur);
  465. $(".durationDisplay").html('<i class="clock icon"></i>' + pos.trim() + " / " + dur.trim());
  466. }
  467. function secondsToHMS(sec){
  468. let totalSeconds = sec;
  469. let hours = Math.floor(totalSeconds / 3600);
  470. totalSeconds %= 3600;
  471. let minutes = Math.floor(totalSeconds / 60);
  472. let seconds = totalSeconds % 60;
  473. if (hours > 0){
  474. hours = String(hours).padStart(2, "0");
  475. }else{
  476. hours = 0;
  477. }
  478. seconds = Math.round(seconds);
  479. minutes = String(minutes).padStart(2, "0");
  480. seconds = String(seconds).padStart(2, "0");
  481. if (hours != 0){
  482. return hours + ":" + minutes + ":" + seconds;
  483. }else{
  484. return minutes + ":" + seconds;
  485. }
  486. }
  487. function togglePlay(object){
  488. if (player.paused == true){
  489. player.play();
  490. $(object).html("<i class='pause icon'></i>");
  491. }else{
  492. player.pause();
  493. $(object).html("<i class='play icon'></i>");
  494. }
  495. }
  496. function toggleRepeat(object){
  497. if ($(object).hasClass("modeEnabled")){
  498. $(object).removeClass("modeEnabled");
  499. repeatMode = false;
  500. setStorage("repeatModeEmbedded","false");
  501. player.loop = false;
  502. }else{
  503. $(object).addClass("modeEnabled");
  504. repeatMode = true;
  505. setStorage("repeatModeEmbedded","true");player.loop = true;
  506. }
  507. }
  508. function updateVolControlDisplay(percentage){
  509. var adjWidth = percentage * 100 + "%";
  510. $("#volControl").css("width",adjWidth);
  511. $("#volControl").attr("vol",percentage);
  512. }
  513. function getCurrentGlobVol(){
  514. //console.log(localStorage.getItem("global_volume"));
  515. if (localStorage.getItem("global_volume") === null || localStorage.getItem("global_volume") == ""){
  516. return 0;
  517. }
  518. return parseFloat(localStorage.getItem("global_volume"));
  519. }
  520. function setStorage(configName,configValue){
  521. $.ajax({
  522. type: 'GET',
  523. url: "/system/file_system/preference",
  524. data: {key: "Music/" + configName,value:configValue},
  525. success: function(data){},
  526. async:true
  527. });
  528. return true;
  529. }
  530. function loadStorage(configName){
  531. var result = "";
  532. $.ajax({
  533. type: 'GET',
  534. url: "/system/file_system/preference",
  535. data: {key: "Music/" + configName},
  536. success: function(data){
  537. if (data.error !== undefined){
  538. result = "";
  539. }else{
  540. result = data;
  541. }
  542. },
  543. error: function(data){result = "";},
  544. async:false,
  545. timeout: 3000
  546. });
  547. return result;
  548. }
  549. </script>
  550. </body>
  551. </html>