embedded.html 18 KB

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