# Media and casting - JavaScript bridge API

> The lock-screen media notification your page drives, and handing a video to a TV.

- **Applies to:** AppMint
- **Source:** extracted from the app runtime and its per-method example files; this page is generated from them.
- **HTML:** https://freewebtoapk.com/docs/api/media

### `navigator.mediaSession`

```js
navigator.mediaSession + MediaMetadata
```

**Example**

Shows what your page is playing in a media notification and on the lock screen, with Previous / Play-Pause / Next / Stop buttons that call back into your page. Uses the normal Media Session API.

**Returns:** nothing; button presses call the handlers you set with `setActionHandler`, with `{ action }` as the standard says. **Needs:** turn on **Media Notification** in Step 4 (Access) when you build. AI-built apps get it switched on for you when the code uses `navigator.mediaSession`.

```js
var audio = document.getElementById('player');
var tracks = [
  { src: 'https://example.com/a.mp3', title: 'Morning', artist: 'Band', art: 'https://example.com/a.jpg' },
  { src: 'https://example.com/b.mp3', title: 'Evening', artist: 'Band', art: 'https://example.com/b.jpg' }
];
var index = 0;
var handlersReady = false;

function setupHandlers() {
  if (handlersReady || !('mediaSession' in navigator)) return;
  handlersReady = true;
  var ms = navigator.mediaSession;
  ms.setActionHandler('play', function () { audio.play(); });
  ms.setActionHandler('pause', function () { audio.pause(); });
  ms.setActionHandler('nexttrack', function () { playTrack(index + 1); });
  ms.setActionHandler('previoustrack', function () { playTrack(index - 1); });
  ms.setActionHandler('stop', function () { audio.pause(); audio.currentTime = 0; });
}

function playTrack(i) {
  index = (i + tracks.length) % tracks.length;
  var t = tracks[index];
  audio.src = t.src;
  audio.play();
  setupHandlers();   // on the first play, not at page load (see Notes)
  if ('mediaSession' in navigator && window.MediaMetadata) {
    navigator.mediaSession.metadata = new MediaMetadata({
      title: t.title, artist: t.artist, album: '',
      artwork: [{ src: t.art, sizes: '512x512', type: 'image/jpeg' }]
    });
  }
}

document.getElementById('play-btn').addEventListener('click', function () { playTrack(0); });
```

Keep the play/pause button and the progress bar right:

```js
function publishState() {
  if (!('mediaSession' in navigator)) return;
  navigator.mediaSession.playbackState = audio.paused ? 'paused' : 'playing';   // set this FIRST
  if (audio.duration > 0 && navigator.mediaSession.setPositionState) {
    navigator.mediaSession.setPositionState({
      duration: audio.duration, position: audio.currentTime, playbackRate: 1
    });
  }
}
audio.addEventListener('play', publishState);
audio.addEventListener('pause', publishState);
audio.addEventListener('seeked', publishState);
```

**Notes:**
- In the app, `navigator.mediaSession` is installed before the page's first script, so handlers and metadata can be set at any time - at the top of the page or when playback starts. It exists in the main page, not inside iframes.
- Supported actions: `play`, `pause`, `nexttrack`, `previoustrack`, `stop`. Seeking from the notification is not supported, so `seekto` never fires.
- Setting `playbackState` keeps the position from your last `setPositionState` call; call `setPositionState` again when the position really changes (a seek).
- Only the **last** artwork image is used. It must be a full `https://` address on the internet (the app downloads it, 4 s limit); a file inside your app ZIP does not show.
- `<audio>` / `<video>` elements are tracked for you: when the page handles a button neither with `setActionHandler` nor with its own `window.WebToApkOnMediaAction`, Play / Pause / Stop control the media element that is playing (else the one played last, else the first).
- To remove the notification, call `WebToApk.clearMediaNotification()` (setting `metadata = null` does not remove it).
- A page that defines `window.WebToApkOnMediaAction = function (action) { ... }` (actions `play`, `pause`, `next`, `prev`, `stop`) gets every button press there too - the app no longer overwrites it. Use one of the two, not both, or a press runs twice.

### `castMedia`

```js
window.WebToApk.castMedia(mediaUrl: String)
```

Offers [mediaUrl] to whatever on this phone can play it on another screen - Chromecast, a Smart TV app, a local player - through the system chooser. The type is taken from the URL ([CastMediaType]): an audio file (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) or an audio `data:` URL is offered to audio apps, anything else - video files, stream manifests, extension-less URLs - to video apps. This is a hand-off, not a cast session: the app does not stay in control of playback, and there is no callback. A page that needs a real session should use the Cast SDK in JavaScript instead.

**Example**

Offers a video or audio address to the apps on the phone that can play it on another screen (a Chromecast app, a Smart TV app, a video player), through the Android "Cast to..." chooser.

**Returns:** nothing, and no callback. **Needs:** nothing to switch on. The phone needs an app that accepts the link.

```js
function castToTv(url) {
  if (window.WebToApk && typeof window.WebToApk.castMedia === 'function') {
    window.WebToApk.castMedia(url);          // opens the "Cast to..." chooser
  } else {
    alert('Casting is only available in the app');
  }
}

document.getElementById('cast-btn').addEventListener('click', function () {
  var video = document.querySelector('video');
  var url = video && video.currentSrc ? video.currentSrc : 'https://example.com/movie.mp4';
  castToTv(url);
});
```

**Notes:**
- This is a hand-off, not a cast session: your page does not control playback after the user picks an app.
- The address must be a full `https://` link that other apps can open. A file inside your app ZIP, a `blob:` URL or a web page address will not play.
- The type comes from the link: an audio file (`.mp3`, `.m4a`, `.aac`, `.ogg`, `.opus`, `.wav`, `.flac` …) or an `audio/` `data:` URL is offered to audio apps; anything else - video files, `.m3u8` streams, links without a file extension - to video apps.

### `clearMediaNotification`

```js
window.WebToApk.clearMediaNotification()
```

Lets web content drive the Android media-style notification and receive transport control callbacks (play/pause/next/prev) from the lock screen and notification shade.  Usage from JavaScript:   window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg')   window.WebToApk.setPlaybackState(true, 30000, 240000)   // playing, pos ms, dur ms   window.WebToApk.clearMediaNotification()  Callback from Android → JS:   window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

_Described by its group, Media Notification Bridge, rather than on its own._

**Example**

Removes the media notification and its lock-screen controls, for example when the user closes your player.

**Returns:** nothing. **Needs:** turn on **Media Notification** in Step 4 (Access) when you build. Without it the call does nothing.

There is no standard web call for this, so use the bridge:

```js
function closePlayer() {
  var audio = document.getElementById('player');
  audio.pause();
  audio.currentTime = 0;
  if (window.WebToApk && typeof window.WebToApk.clearMediaNotification === 'function') {
    window.WebToApk.clearMediaNotification();
  }
}

document.getElementById('close-player').addEventListener('click', closePlayer);

// The notification's own Stop button already removes it; just stop your audio:
if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('stop', function () {
    var audio = document.getElementById('player');
    audio.pause();
    audio.currentTime = 0;
  });
}
```

**Notes:** Use this for "stop", not for "pause" (for pause, send `setPlaybackState(false, ...)` so the Play button stays). Setting `navigator.mediaSession.metadata = null` does not remove the notification. The next `setMediaMetadata` / `setPlaybackState` (or new `navigator.mediaSession` metadata / state) brings the notification and the lock-screen controls back.

### `setMediaMetadata`

```js
window.WebToApk.setMediaMetadata(title: String, artist: String, artworkUrl: String)
```

Lets web content drive the Android media-style notification and receive transport control callbacks (play/pause/next/prev) from the lock screen and notification shade.  Usage from JavaScript:   window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg')   window.WebToApk.setPlaybackState(true, 30000, 240000)   // playing, pos ms, dur ms   window.WebToApk.clearMediaNotification()  Callback from Android → JS:   window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

_Described by its group, Media Notification Bridge, rather than on its own._

**Example**

Sets the title, artist and cover picture of the media notification and lock-screen player. Pages normally set `navigator.mediaSession.metadata`, which calls this for you.

**Returns:** nothing. **Needs:** turn on **Media Notification** in Step 4 (Access) when you build. Without it the call does nothing.

The public way (use this):

```js
if ('mediaSession' in navigator && window.MediaMetadata) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Episode 12', artist: 'My Podcast',
    artwork: [{ src: 'https://example.com/cover.jpg', sizes: '512x512' }]
  });
}
```

The raw call. Arguments: `title, artist, artworkUrl` (use `''` for no picture):

```js
function showNowPlaying(track) {
  if (!(window.WebToApk && typeof window.WebToApk.setMediaMetadata === 'function')) return;
  window.WebToApk.setMediaMetadata(track.title, track.artist || '', track.cover || '');
  window.WebToApk.setPlaybackState(true, 0, Math.round(track.seconds * 1000));
}

// Buttons in the notification come back through the standard API:
if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('nexttrack', function () { playNext(); });
  navigator.mediaSession.setActionHandler('previoustrack', function () { playPrevious(); });
}
```

**Notes:**
- The notification appears (or updates) with this call. An empty title shows the app name.
- `artworkUrl` must be a full `https://` address on the internet. The app downloads it (4-second limit); if that fails, the app icon is shown.
- Receive button presses with `navigator.mediaSession.setActionHandler` (see `navigator.mediaSession`), or with your own `window.WebToApkOnMediaAction = function (action) { ... }` (`play`, `pause`, `next`, `prev`, `stop`) - use one of the two. After `clearMediaNotification()`, the next call to this (or `setPlaybackState`) brings the notification and the lock-screen controls back.

### `setPlaybackState`

```js
window.WebToApk.setPlaybackState(isPlaying: Boolean, positionMs: Long, durationMs: Long)
```

Lets web content drive the Android media-style notification and receive transport control callbacks (play/pause/next/prev) from the lock screen and notification shade.  Usage from JavaScript:   window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg')   window.WebToApk.setPlaybackState(true, 30000, 240000)   // playing, pos ms, dur ms   window.WebToApk.clearMediaNotification()  Callback from Android → JS:   window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

_Described by its group, Media Notification Bridge, rather than on its own._

**Example**

Tells the media notification whether you are playing or paused, and where you are in the track. Pages normally set `navigator.mediaSession.playbackState` and call `setPositionState`, which call this for you.

**Returns:** nothing. **Needs:** turn on **Media Notification** in Step 4 (Access) when you build. Without it the call does nothing.

The public way (use this):

```js
var audio = document.getElementById('player');
function publish() {
  if (!('mediaSession' in navigator)) return;
  navigator.mediaSession.playbackState = audio.paused ? 'paused' : 'playing';
  if (audio.duration > 0 && navigator.mediaSession.setPositionState) {
    navigator.mediaSession.setPositionState({ duration: audio.duration, position: audio.currentTime });
  }
}
audio.addEventListener('play', publish);
audio.addEventListener('pause', publish);
```

The raw call. Arguments: `isPlaying` (true/false), `positionMs`, `durationMs` (whole milliseconds):

```js
function publishRaw(audio) {
  if (!(window.WebToApk && typeof window.WebToApk.setPlaybackState === 'function')) return;
  window.WebToApk.setPlaybackState(
    !audio.paused,
    Math.floor(audio.currentTime * 1000),
    Math.floor((audio.duration || 0) * 1000)
  );
}
```

**Notes:** `true` shows the Pause button and keeps the notification from being swiped away; `false` shows Play. Call `setMediaMetadata` first, so the notification has a title. To remove the notification when the user stops, call `clearMediaNotification()`.

