# Media Player

HTML5 audio and video player with full playback control, volume, seek, loop, and status API.

## Live Demo

<iframe id="mp-demo" src="/online/webapp/media-player" width="100%" height="400" frameborder="0" style="border:1px solid #ccc; border-radius:4px;"></iframe>

<script>window._wsConnect('mp-demo', 'mpSocket');</script>

## Embed

```html
<iframe src="https://sgapps.io/online/webapp/media-player"
    width="100%" height="400" frameborder="0"></iframe>
```

### Open with a Media URL

```html
<iframe src="https://sgapps.io/online/webapp/media-player/url/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ="
    width="100%" height="400" frameborder="0"></iframe>
```

**Supported formats:** MP4, WebM, OGG, AVI, MP3, WAV, MIDI, Matroska

---

## Events Reference

---

### `play`, `pause`, `stop` -- Playback Control

Basic transport controls.

- **`play`** starts playback from the current position
- **`pause`** pauses without resetting position
- **`stop`** pauses and seeks back to the beginning

```js
socket.fire("webapp::instance::request", "play");
socket.fire("webapp::instance::request", "pause");
socket.fire("webapp::instance::request", "stop");
```

<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','play')">Try: Play</button>
<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','pause')">Try: Pause</button>
<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','stop')">Try: Stop</button>

---

### `seek` -- Seek to Position

Jump to a specific time position in the media.

| Arg | Type | Description |
|-----|------|-------------|
| `timeSeconds` | number | Position in seconds |
| `callback` | function | `(err)` |

```js
socket.fire("webapp::instance::request", "seek", 30, function () {
    console.log("Seeked to 30s");
});
```

---

### `volume` -- Get or Set Volume

When called with a number, sets the volume. When called with only a callback, returns the current volume.

| Arg | Type | Description |
|-----|------|-------------|
| `level` | number (optional) | 0 (muted) to 1 (full volume) |
| `callback` | function | `(err, currentVolume: number)` |

```js
// Set to 50%
socket.fire("webapp::instance::request", "volume", 0.5);

// Get current
socket.fire("webapp::instance::request", "volume", function (err, vol) {
    console.log("Volume:", Math.round(vol * 100) + "%");
});
```

<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','volume',0.25)">Try: 25% Volume</button>
<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','volume',1.0)">Try: 100% Volume</button>

---

### `mute` -- Get or Set Mute

| Arg | Type | Description |
|-----|------|-------------|
| `muted` | boolean (optional) | `true` to mute, `false` to unmute |
| `callback` | function | `(err, isMuted: boolean)` |

```js
socket.fire("webapp::instance::request", "mute", true);  // mute
socket.fire("webapp::instance::request", "mute", false); // unmute
```

---

### `getStatus` -- Get Playback Status

Returns a snapshot of the player's current state. Useful for building custom UI overlays.

```js
socket.fire("webapp::instance::request", "getStatus", function (err, status) {
    console.log(status);
    // {
    //   paused: false,
    //   currentTime: 12.5,
    //   duration: 180,
    //   volume: 0.8,
    //   muted: false,
    //   playbackRate: 1,
    //   loop: false,
    //   readyState: 4,
    //   videoWidth: 1920,
    //   videoHeight: 1080
    // }
});
```

<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','getStatus',function(e,s){alert(JSON.stringify(s,null,2))})">Try: Get Status</button>

---

### `source` -- Set Media Source

Load a new media file. If the URL starts with `/`, it's resolved through the file system's `readStreamUrl`. Otherwise it's used as-is.

| Arg | Type | Description |
|-----|------|-------------|
| `url` | string | Media URL or file path |
| `callback` | function | `(err)` |

```js
socket.fire("webapp::instance::request", "source", "https://example.com/song.mp3");
```

---

### `playbackRate` -- Get or Set Speed

| Arg | Type | Description |
|-----|------|-------------|
| `rate` | number (optional) | 0.5 = half speed, 1 = normal, 2 = double speed |
| `callback` | function | `(err, currentRate: number)` |

```js
socket.fire("webapp::instance::request", "playbackRate", 1.5); // 1.5x speed
```

<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','playbackRate',0.5)">Try: 0.5x</button>
<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','playbackRate',1)">Try: 1x</button>
<button onclick="window._ws('mpSocket')&&window.mpSocket.fire('webapp::instance::request','playbackRate',2)">Try: 2x</button>

---

### `loop` -- Get or Set Loop Mode

| Arg | Type | Description |
|-----|------|-------------|
| `enabled` | boolean (optional) | `true` to loop, `false` to play once |
| `callback` | function | `(err, isLoop: boolean)` |

```js
socket.fire("webapp::instance::request", "loop", true); // enable looping
```

---

### `fullscreen` -- Enter Fullscreen

Requests fullscreen mode for the video element.

```js
socket.fire("webapp::instance::request", "fullscreen");
```

---

## Complete Example

```html
<!DOCTYPE html>
<html>
<head><title>Custom Video Player</title></head>
<body>
    <div style="display:flex;gap:8px;padding:10px;flex-wrap:wrap;">
        <button id="play">Play</button>
        <button id="pause">Pause</button>
        <button id="status">Status</button>
        <input id="seek" type="range" min="0" max="100" value="0">
        <span id="time">0:00</span>
    </div>

    <iframe id="player" src="https://sgapps.io/online/webapp/media-player"
        width="100%" height="400" frameborder="0"></iframe>

    <script src="https://sgapps.io/components/window-socket/index.js"></script>
    <script>
        var socket = new WindowSocket(document.getElementById('player').contentWindow);
        socket.start();

        socket.on("webapp::connection::ping", function () {
            socket.fire("webapp::instance::embed-mode", true);
        });

        document.getElementById('play').onclick = function () {
            socket.fire("webapp::instance::request", "play");
        };
        document.getElementById('pause').onclick = function () {
            socket.fire("webapp::instance::request", "pause");
        };
        document.getElementById('status').onclick = function () {
            socket.fire("webapp::instance::request", "getStatus", function (err, s) {
                document.getElementById('time').textContent =
                    Math.floor(s.currentTime) + 's / ' + Math.floor(s.duration) + 's';
            });
        };
        document.getElementById('seek').oninput = function () {
            socket.fire("webapp::instance::request", "getStatus", function (err, s) {
                var time = (parseInt(document.getElementById('seek').value) / 100) * s.duration;
                socket.fire("webapp::instance::request", "seek", time);
            });
        };
    </script>
</body>
</html>
```
