embedded.html 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  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. //Load song title into the display area
  211. updateDisplayInformation(songInfo[0],songInfo[3]);
  212. //Create the playlist for all the files in the same directory
  213. createPlayList();
  214. //Initiate the current playSong in the songList
  215. updatePlayingSongSelection();
  216. })
  217. /*
  218. $.get("../Music/getMeta?file=" + encodeURIComponent(playingFileInfo.filepath),function(data){
  219. //console.log(data);
  220. songList = data;
  221. for (var i = 0; i < data.length; i++){
  222. if (data[i][0] == playingFileInfo.filename){
  223. songInfo = data[i];
  224. //Set the audio element src
  225. player.src = "../media?file=" + encodeURIComponent(data[i][1]);
  226. //Update window title
  227. ao_module_setWindowTitle(data[i][0]);
  228. }
  229. }
  230. //Load song title into the display area
  231. updateDisplayInformation(songInfo[0],songInfo[3]);
  232. //Create the playlist for all the files in the same directory
  233. createPlayList();
  234. //Initiate the current playSong in the songList
  235. updatePlayingSongSelection();
  236. });
  237. */
  238. //Get the song title from meta data
  239. //var songInfo = JSON.parse($("#meta_playingSong").text().trim());
  240. //var songList = JSON.parse($("#meta_dirSongList").text().trim());
  241. //ao_module_setWindowTitle(ao_module_codec.decodeUmFilename(songInfo[0]));
  242. //Define supporting function for trunc.
  243. String.prototype.trunc = String.prototype.trunc ||
  244. function(n){
  245. return (this.length > n) ? this.substr(0, n-1) + '&hellip;' : this;
  246. };
  247. //Setup listen events to global volume
  248. setInterval(function(){
  249. var globvol = getCurrentGlobVol();
  250. updateVolControlDisplay(globvol);
  251. player.volume = globvol;
  252. if (player.paused == true){
  253. //Check if anytime that the button UI is not in sync with the current playing status. Update it if found.
  254. $(".playBtn").html("<i class='play icon'></i>");
  255. }else{
  256. $(".playBtn").html("<i class='pause icon'></i>");
  257. }
  258. },1000);
  259. updateVolControlDisplay(getCurrentGlobVol());
  260. //Add volume bar change event listener
  261. $("#volControlOverlay").on("click",function(e){
  262. var x = e.pageX - $('#volControlOverlay').offset().left;
  263. var y = e.pageY - $('#volControlOverlay').offset().top;
  264. var height = $('#volControlOverlay').height();
  265. var newvol = Math.round(((height - y) / height)*20)/20;
  266. localStorage.setItem("global_volume",newvol);
  267. player.volume = newvol;
  268. updateVolControlDisplay(newvol);
  269. });
  270. //Also add draging bar for mouse operations
  271. player.onended = function(){
  272. if (!repeatMode){
  273. $(".playBtn").html("<i class='play icon'></i>");
  274. }
  275. }
  276. var dragging = false;
  277. $("#volControlOverlay").on("mousedown",function(){
  278. dragging = true;
  279. });
  280. $("#volControlOverlay").on("mousemove",function(e){
  281. if (dragging){
  282. var y = e.pageY - $('#volControlOverlay').offset().top;
  283. var height = $('#volControlOverlay').height();
  284. var newvol = Math.round(((height - y) / height)*100)/100;
  285. //console.log(newvol);
  286. localStorage.setItem("global_volume",newvol);
  287. player.volume = newvol;
  288. updateVolControlDisplay(newvol);
  289. }
  290. });
  291. $("#volControlOverlay").on("mouseup",function(){
  292. if (dragging){
  293. dragging = false;
  294. }
  295. });
  296. $("body").on("mouseup",function(){
  297. if (dragging){
  298. dragging = false;
  299. }
  300. });
  301. //Audio element custom UI listeners
  302. document.getElementById("progressControl").addEventListener("click",function(e){
  303. //Click on the progress control bar, update the audio playing location
  304. var x = e.pageX - $('#progressControl').offset().left;
  305. var w = $("#progressControl").width();
  306. var alength = player.duration;
  307. var targetPos = (x / w * alength);
  308. player.currentTime = targetPos;
  309. });
  310. player.addEventListener("timeupdate", function(){
  311. var currentTime = player.currentTime;
  312. var duration = player.duration;
  313. var progressPercentage = currentTime / duration * 100 + "%";
  314. $("#playbackProgress").css("width",progressPercentage);
  315. if (!mediaExchanging){
  316. updateDurationDisplay(currentTime,duration);
  317. }
  318. });
  319. //Load repeat mode from storage
  320. var repeatMode = false;
  321. repeatMode = loadStorage("repeatModeEmbedded");
  322. if (repeatMode != ""){
  323. repeatMode = (repeatMode == "true");
  324. }
  325. if (repeatMode){
  326. $("#repeatModeBtn").addClass("modeEnabled");
  327. player.loop = true;
  328. }
  329. //End of startup seuqence
  330. function updatePlayingSongSelection(){
  331. $(".listitem.selected").removeClass("selected");
  332. $(".listitem").each(function(){
  333. if ($(this).attr("filename") == songInfo[0]){
  334. $(this).addClass("selected");
  335. }
  336. });
  337. }
  338. function showRelatedFileList(){
  339. if (ao_module_virtualDesktop){
  340. if (listShown){
  341. ao_module_setWindowSize(360,240);
  342. listShown = false;
  343. }else{
  344. ao_module_setWindowSize(360,520);
  345. listShown = true;
  346. }
  347. }
  348. }
  349. function lastSong(){
  350. //Switch to the next song in list
  351. player.pause();
  352. mediaExchanging = true;
  353. $(".durationDisplay").html('<i class="clock icon"></i> Loading Media');
  354. var nextSong = -1; //There is no next song in the current directory
  355. for (var i =0; i < songList.length; i++){
  356. if (songList[i][0] == songInfo[0]){
  357. //Filename are the same
  358. if (i-1 < 0){
  359. //This is the last song in list
  360. nextSong = songList[songList.length - 1]
  361. }else{
  362. //Play previous song
  363. nextSong = songList[i-1]
  364. }
  365. }
  366. }
  367. if (nextSong == -1){
  368. //There is no more similar file with similar extensions or there are no other supported files in the same directory.
  369. player.currentTime = 0;
  370. updateDisplayInformation(ao_module_codec.decodeUmFilename(songInfo[0]),songInfo[3]);
  371. player.play();
  372. setTimeout(function(){
  373. mediaExchanging = false;
  374. },300);
  375. return;
  376. }
  377. loadSongFromSongInfo(nextSong);
  378. player.play();
  379. setTimeout(function(){
  380. mediaExchanging = false;
  381. },300);
  382. updatePlayingSongSelection();
  383. }
  384. function createPlayList(){
  385. $(".sameDirFileList").html("");
  386. var template = '<div class="listitem" filename="{rawname}" onClick="playThis(this);"><i class="file audio outline icon"></i> {songname} ({filesize})</div>';
  387. for (var i = 0; i < songList.length; i++){
  388. var box = template;
  389. box = box.split("{rawname}").join(songList[i][0]);
  390. box = box.split("{songname}").join(ao_module_codec.decodeUmFilename(songList[i][0]));
  391. box = box.split("{filesize}").join(songList[i][3]);
  392. //console.log(box);
  393. $(".sameDirFileList").append(box);
  394. }
  395. }
  396. function playThis(object){
  397. var songName = $(object).attr("filename");
  398. $(".listitem.selected").removeClass("selected");
  399. $(object).addClass("selected");
  400. for (var i =0; i < songList.length; i++){
  401. if (songList[i][0] == songName){
  402. //This is the song that the user request to play.
  403. player.pause();
  404. mediaExchanging = true;
  405. $(".durationDisplay").html('<i class="clock icon"></i> Loading Media');
  406. loadSongFromSongInfo(songList[i]);
  407. player.play();
  408. setTimeout(function(){
  409. mediaExchanging = false;
  410. },300);
  411. break;
  412. }
  413. }
  414. }
  415. function nextSong(){
  416. //Switch to the next song in list
  417. player.pause();
  418. mediaExchanging = true;
  419. $(".durationDisplay").html('<i class="clock icon"></i> Loading Media');
  420. var nextSong = -1; //There is no next song in the current directory
  421. for (var i =0; i < songList.length; i++){
  422. if (songList[i][0] == songInfo[0]){
  423. //Filename are the same
  424. if (i+1 >= songList.length){
  425. //This is the last song in list
  426. nextSong = songList[0]
  427. }else{
  428. //Play next song
  429. nextSong = songList[i+1]
  430. }
  431. }
  432. }
  433. if (nextSong == -1){
  434. //There is no more similar file with similar extensions or there are no other supported files in the same directory.
  435. player.currentTime = 0;
  436. updateDisplayInformation(ao_module_codec.decodeUmFilename(songInfo[0]), songInfo[3]);
  437. player.play();
  438. setTimeout(function(){
  439. mediaExchanging = false;
  440. },300);
  441. return;
  442. }
  443. loadSongFromSongInfo(nextSong);
  444. player.play();
  445. setTimeout(function(){
  446. mediaExchanging = false;
  447. },300);
  448. updatePlayingSongSelection();
  449. }
  450. function blobToDataURL(blob, callback) {
  451. var a = new FileReader();
  452. a.onload = function(e) {callback(e.target.result);}
  453. a.readAsDataURL(blob);
  454. }
  455. function loadSongFromSongInfo(playSongInfo){
  456. var songName = ao_module_codec.decodeUmFilename(playSongInfo[0]);
  457. var songPath = filterExternalStoragePath(playSongInfo[1]);
  458. var fileSize = playSongInfo[3];
  459. $(player).attr('src',"/media?file=" + encodeURIComponent(songPath));
  460. ao_module_setWindowTitle(songName);
  461. songInfo = playSongInfo;
  462. updateDisplayInformation(ao_module_codec.decodeUmFilename(songInfo[0]),songInfo[3]);
  463. }
  464. function updateDisplayInformation(songname, filesize){
  465. $("#songtitle").html("<i class='music icon'></i>" + songname)
  466. $("#fileSize").html("<i class='file outline icon'></i> " + filesize);
  467. }
  468. function filterExternalStoragePath(filepath){
  469. return filepath;
  470. }
  471. function updateDurationDisplay(pos,dur){
  472. pos = secondsToHMS(pos);
  473. dur = secondsToHMS(dur);
  474. $(".durationDisplay").html('<i class="clock icon"></i>' + pos.trim() + " / " + dur.trim());
  475. }
  476. function secondsToHMS(sec){
  477. let totalSeconds = sec;
  478. let hours = Math.floor(totalSeconds / 3600);
  479. totalSeconds %= 3600;
  480. let minutes = Math.floor(totalSeconds / 60);
  481. let seconds = totalSeconds % 60;
  482. if (hours > 0){
  483. hours = String(hours).padStart(2, "0");
  484. }else{
  485. hours = 0;
  486. }
  487. seconds = Math.round(seconds);
  488. minutes = String(minutes).padStart(2, "0");
  489. seconds = String(seconds).padStart(2, "0");
  490. if (hours != 0){
  491. return hours + ":" + minutes + ":" + seconds;
  492. }else{
  493. return minutes + ":" + seconds;
  494. }
  495. }
  496. function togglePlay(object){
  497. if (player.paused == true){
  498. player.play();
  499. $(object).html("<i class='pause icon'></i>");
  500. }else{
  501. player.pause();
  502. $(object).html("<i class='play icon'></i>");
  503. }
  504. }
  505. function toggleRepeat(object){
  506. if ($(object).hasClass("modeEnabled")){
  507. $(object).removeClass("modeEnabled");
  508. repeatMode = false;
  509. setStorage("repeatModeEmbedded","false");
  510. player.loop = false;
  511. }else{
  512. $(object).addClass("modeEnabled");
  513. repeatMode = true;
  514. setStorage("repeatModeEmbedded","true");player.loop = true;
  515. }
  516. }
  517. function updateVolControlDisplay(percentage){
  518. var adjWidth = percentage * 100 + "%";
  519. $("#volControl").css("width",adjWidth);
  520. $("#volControl").attr("vol",percentage);
  521. }
  522. function getCurrentGlobVol(){
  523. //console.log(localStorage.getItem("global_volume"));
  524. if (localStorage.getItem("global_volume") === null || localStorage.getItem("global_volume") == ""){
  525. return 0;
  526. }
  527. return parseFloat(localStorage.getItem("global_volume"));
  528. }
  529. function setStorage(configName,configValue){
  530. $.ajax({
  531. type: 'GET',
  532. url: "/system/file_system/preference",
  533. data: {key: "Music/" + configName,value:configValue},
  534. success: function(data){},
  535. async:true
  536. });
  537. return true;
  538. }
  539. function loadStorage(configName){
  540. var result = "";
  541. $.ajax({
  542. type: 'GET',
  543. url: "/system/file_system/preference",
  544. data: {key: "Music/" + configName},
  545. success: function(data){
  546. if (data.error !== undefined){
  547. result = "";
  548. }else{
  549. result = data;
  550. }
  551. },
  552. error: function(data){result = "";},
  553. async:false,
  554. timeout: 3000
  555. });
  556. return result;
  557. }
  558. </script>
  559. </body>
  560. </html>