audio-player.html 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Audio Player Controls — Arozcast Example</title>
  6. <script src="../arozcast.js"></script>
  7. <style>
  8. body { background:#111; color:#ddd; font-family:sans-serif; padding:24px; max-width:480px; }
  9. h2 { margin:0 0 6px; }
  10. p { margin:0 0 14px; color:#888; }
  11. code { color:#aaa; background:#222; padding:1px 5px; border-radius:3px; }
  12. input, button { background:#222; color:#ddd; border:1px solid #444; border-radius:4px; padding:5px 10px; }
  13. button { cursor:pointer; }
  14. button:hover { background:#333; }
  15. hr { border:none; border-top:1px solid #333; margin:16px 0; }
  16. #bar { background:#333; height:8px; border-radius:4px; cursor:pointer; margin:10px 0 4px; }
  17. #fill { background:#aaa; height:100%; border-radius:4px; width:0; pointer-events:none; }
  18. #time { color:#888; font-size:13px; }
  19. #controls { display:none; }
  20. #controls button { margin:0 4px 8px 0; }
  21. #ended-msg { color:#888; font-size:13px; display:none; }
  22. </style>
  23. </head>
  24. <body>
  25. <h2>Audio Player Controls</h2>
  26. <p>
  27. Demonstrates <code>seek()</code>, mute toggle, <code>isConnected()</code>,
  28. repeat modes, and the <code>ended</code> event.<br>
  29. Place your audio at <code>user:/example.mp3</code>.
  30. </p>
  31. <p>
  32. Room code:
  33. <input id="code" maxlength="4" size="5" placeholder="1234">
  34. <button onclick="doConnect()">Connect</button>
  35. </p>
  36. <p id="status">Not connected</p>
  37. <div id="controls">
  38. <!-- Transport -->
  39. <button onclick="cast.play()">Play</button>
  40. <button onclick="cast.pause()">Pause</button>
  41. <button onclick="doDisconnect()">Disconnect</button>
  42. <hr>
  43. <!-- Progress bar — click to seek(time) to that position -->
  44. <div id="bar" onclick="onSeekBarClick(event)">
  45. <div id="fill"></div>
  46. </div>
  47. <span id="time">0:00 / 0:00</span>
  48. <hr>
  49. <!-- Volume slider + mute toggle -->
  50. Volume: <input type="range" id="vol" min="0" max="100" value="80"
  51. oninput="onVolumeChange()">
  52. <button id="mute-btn" onclick="toggleMute()">Mute</button>
  53. <hr>
  54. <!-- Repeat — cycles none → all → one, synced via setRepeat() -->
  55. <button id="repeat-btn" onclick="cycleRepeat()">Repeat: off</button>
  56. <p id="ended-msg">⏹ Track finished — this is where you would load the next track.</p>
  57. <hr>
  58. <!-- cast.code and cast.connected readable properties -->
  59. Room: <span id="room-code">—</span> &nbsp;|&nbsp;
  60. Connected: <span id="connected-state">false</span>
  61. </div>
  62. <script>
  63. var _s = document.querySelector('script[src*="arozcast.js"]').src;
  64. var ao_root = _s.slice(0, _s.indexOf('Arozcast/arozcast.js'));
  65. var FILE = 'user:/example.mp3';
  66. var muted = false;
  67. var repeatMode = 'none'; // 'none' | 'all' | 'one'
  68. var duration = 0;
  69. var currTime = 0;
  70. var cast = new ArozCast({ aoRoot: ao_root });
  71. // ── Events ─────────────────────────────────────────────────────────────────
  72. cast.on('connect', function(e) {
  73. status('Connected to room ' + e.code);
  74. document.getElementById('controls').style.display = 'block';
  75. document.getElementById('ended-msg').style.display = 'none';
  76. refreshProps();
  77. cast.load({
  78. name: 'example.mp3', type: 'audio',
  79. src: ao_root + 'media?file=' + encodeURIComponent(FILE),
  80. filepath: FILE,
  81. });
  82. cast.setVolume(+document.getElementById('vol').value, muted);
  83. cast.setRepeat(repeatMode); // re-sync repeat on every connect / reconnect
  84. cast.play();
  85. });
  86. cast.on('disconnect', function() { status('Connection lost — reconnecting…'); refreshProps(); });
  87. cast.on('reconnecting', function(e) { status('Reconnecting, attempt ' + e.attempt + '…'); });
  88. cast.on('giveup', function() { status('Could not reconnect'); refreshProps(); });
  89. // status event → drive the seek bar and time display
  90. cast.on('status', function(s) {
  91. duration = s.duration;
  92. currTime = s.currentTime;
  93. updateBar();
  94. refreshProps();
  95. });
  96. // ended event — fired when the track finishes naturally.
  97. // Not fired when repeat === 'one' (receiver loops natively, no ended event).
  98. // For repeat === 'all', this is where you would call cast.load() with the next track.
  99. cast.on('ended', function() {
  100. document.getElementById('ended-msg').style.display = '';
  101. });
  102. // ── Seek bar ────────────────────────────────────────────────────────────────
  103. function onSeekBarClick(e) {
  104. // isConnected() is checked explicitly before sending any command
  105. if (!cast.isConnected()) return;
  106. var t = (e.offsetX / e.currentTarget.offsetWidth) * (duration || 0);
  107. cast.seek(t); // seek(time) — absolute seek in seconds
  108. currTime = t; // optimistic update
  109. updateBar();
  110. }
  111. function updateBar() {
  112. var pct = duration ? Math.min(currTime / duration * 100, 100) : 0;
  113. document.getElementById('fill').style.width = pct + '%';
  114. document.getElementById('time').textContent = fmt(currTime) + ' / ' + fmt(duration);
  115. }
  116. // ── Volume / mute ───────────────────────────────────────────────────────────
  117. function onVolumeChange() {
  118. if (!cast.isConnected()) return;
  119. cast.setVolume(+document.getElementById('vol').value, muted);
  120. }
  121. function toggleMute() {
  122. muted = !muted;
  123. document.getElementById('mute-btn').textContent = muted ? 'Unmute' : 'Mute';
  124. // setVolume(level, true) mutes; setVolume(level, false) unmutes
  125. cast.setVolume(+document.getElementById('vol').value, muted);
  126. }
  127. // ── Repeat ──────────────────────────────────────────────────────────────────
  128. function cycleRepeat() {
  129. var modes = ['none', 'all', 'one'];
  130. repeatMode = modes[(modes.indexOf(repeatMode) + 1) % modes.length];
  131. document.getElementById('repeat-btn').textContent = 'Repeat: ' + repeatMode;
  132. cast.setRepeat(repeatMode);
  133. }
  134. // ── Connect / disconnect ───────────────────────────────────────────────────
  135. function doConnect() {
  136. var code = document.getElementById('code').value.trim();
  137. if (!/^\d{4}$/.test(code)) { alert('Enter a 4-digit code'); return; }
  138. status('Checking room…');
  139. cast.ping(code).then(function(ok) {
  140. if (!ok) { status('Room not found — is Arozcast open?'); return; }
  141. return cast.connect(code);
  142. }).catch(function(err) { status('Error: ' + err.message); });
  143. }
  144. function doDisconnect() {
  145. cast.disconnect();
  146. status('Disconnected');
  147. document.getElementById('controls').style.display = 'none';
  148. duration = currTime = 0;
  149. refreshProps();
  150. }
  151. // ── Helpers ────────────────────────────────────────────────────────────────
  152. function refreshProps() {
  153. document.getElementById('room-code').textContent = cast.code || '—';
  154. document.getElementById('connected-state').textContent = cast.connected ? 'true' : 'false';
  155. }
  156. function status(msg) { document.getElementById('status').textContent = msg; }
  157. function fmt(s) {
  158. s = Math.floor(s || 0);
  159. return Math.floor(s / 60) + ':' + ('0' + s % 60).slice(-2);
  160. }
  161. </script>
  162. </body>
  163. </html>