> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gcore.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Add token auto-refresh to a video player

> Add token auto-refresh to a video player with TokenRefreshPlugin for hls.js and dash.js streams.

The examples below use the [demo token endpoint](/streaming/interaction-with-cdn/token-api-fastedge#demo-endpoint). Replace `TOKEN_API_URL` with an authenticated backend endpoint before deploying to production. The [live demo](https://g-core.github.io/gcore-videoplayer-js/example/protected-content.html) uses the Gcore Video Player tab below and shows token status and refresh log in real time.

![Demo player showing active token status and refresh log with CDN segment requests in devtools](https://mintcdn.com/gcore/mB9xUQPpVbdf6kcA/images/docs/streaming/interaction-with-cdn/token-auto-refresh/cdn-token-demo.png?fit=max\&auto=format\&n=mB9xUQPpVbdf6kcA\&q=85\&s=80616d5cbeb774102a12e36efff3288d)

**Gcore Video Player**

`TokenRefreshPlugin` handles token rotation without a custom URL loader or request interceptor. The plugin reads the initial token from the source URL, rewrites every outgoing request, and calls `getToken` automatically before expiry.

The package is on GitHub at [github.com/G-Core/gcore-videoplayer-js](https://github.com/G-Core/gcore-videoplayer-js).

```html theme={null}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="stylesheet" href="https://player.gvideo.co/v2/assets/latest/index.css" />
</head>
<body>
  <div id="player" style="width:854px; height:480px;"></div>

  <script type="module">
    import {
      Player,
      MediaControl,
      QualityLevels,
      Spinner,
      ErrorScreen,
      TokenRefreshPlugin,
    } from 'https://player.gvideo.co/v2/assets/latest/index.js'

    // Replace with your authenticated backend endpoint.
    // Must return: { token, token_ip, client_ip, expires, url, url_ip }
    const TOKEN_API_URL =
      'https://video-token-102748.fastedge.app/?video=iKbrdNMcS9ylGuw&type=vod&expire=20'

    async function getToken() {
      const res = await fetch(TOKEN_API_URL)
      if (!res.ok) throw new Error(`Token API returned ${res.status}`)
      return res.json()
    }

    // Register TokenRefreshPlugin BEFORE other plugins.
    if (!Player.corePlugins.some(p => p.prototype?.name === 'token_refresh')) {
      Player.registerPlugin(TokenRefreshPlugin)
    }
    if (!Player.corePlugins.some(p => p.prototype?.name === 'media_control')) {
      Player.registerPlugin(MediaControl)
      Player.registerPlugin(QualityLevels)
      Player.registerPlugin(Spinner)
      Player.registerPlugin(ErrorScreen)
    }

    // Fetch initial token BEFORE constructing the Player.
    // The source URL must contain a valid {token}/{expires} path on startup.
    const tokenData = await getToken()

    const player = new Player({
      sources: [{
        // Use tokenData.url_ip (and ipBound: true) for IP-bound tokens.
        source: tokenData.url,
        mimeType: 'application/x-mpegURL',
      }],
      playbackType: 'vod',    // 'vod' | 'live'

      tokenRefresh: {
        getToken,
        ipBound: false,             // true → use url_ip / token_ip
        refreshLeadSeconds: 10,     // fetch replacement token this many seconds before expiry
        onTokenRefreshed(data) {
          console.log('Token refreshed — next expiry:', new Date(data.expires * 1000))
        },
      },
    })

    // attachTo() must be the last step — it triggers plugin initialization.
    player.attachTo(document.getElementById('player'))
  </script>
</body>
</html>
```

**hls.js**

For React, Vue, Angular, or any setup using [hls.js](https://github.com/video-dev/hls.js) directly, configure a custom URL-rewriting loader and a refresh timer.

`TokenRewriteLoader` extends the default hls.js XHR loader and rewrites the `{token}/{expires}` path segments in every request before the connection opens. A chained `setTimeout` scheduler fetches a fresh token before expiry.

```html theme={null}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
  <video id="video" controls style="width:854px; height:480px; background:#000;"></video>

  <script src="https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js"></script>
  <script>
    // Replace with your authenticated backend endpoint.
    // Must return: { token, token_ip, client_ip, expires, url, url_ip }
    const TOKEN_API_URL =
      'https://video-token-102748.fastedge.app/?video=iKbrdNMcS9ylGuw&type=vod&expire=20'

    // Shared state — loader closure reads the latest value after each refresh.
    const tokenState = { token: '', expires: 0 }

    async function fetchToken() {
      const res = await fetch(TOKEN_API_URL)
      if (!res.ok) throw new Error(`Token API returned ${res.status}`)
      return res.json()
    }

    // Matches the {token}/{expires} pair embedded in Gcore CDN URLs.
    const TOKEN_RE = /\/([A-Za-z0-9_-]{6,})\/(1\d{9,})\//

    function rewriteUrl(url) {
      return url.replace(TOKEN_RE, `/${tokenState.token}/${tokenState.expires}/`)
    }

    class TokenRewriteLoader extends Hls.DefaultConfig.loader {
      load(context, config, callbacks) {
        context.url = rewriteUrl(context.url)
        super.load(context, config, callbacks)
      }
    }

    const LEAD_SECONDS = 10
    let refreshTimer = null

    function scheduleRefresh(expires) {
      clearTimeout(refreshTimer)
      const msUntilRefresh = Math.max(1000, (expires - Date.now() / 1000 - LEAD_SECONDS) * 1000)
      refreshTimer = setTimeout(doRefresh, msUntilRefresh)
    }

    async function doRefresh() {
      try {
        const data = await fetchToken()
        // Use data.token_ip for IP-bound mode.
        tokenState.token   = data.token
        tokenState.expires = data.expires
        console.log('Token refreshed — next expiry:', new Date(data.expires * 1000))
        scheduleRefresh(data.expires)
      } catch (err) {
        console.error('Token refresh failed:', err)
        setTimeout(doRefresh, 5000)
      }
    }

    async function init() {
      const data = await fetchToken()

      // Seed state BEFORE loadSource() so the first request is rewritten correctly.
      tokenState.token   = data.token    // or data.token_ip for IP-bound
      tokenState.expires = data.expires

      const hls = new Hls({ loader: TokenRewriteLoader })
      hls.loadSource(data.url)
      hls.attachMedia(document.getElementById('video'))

      scheduleRefresh(data.expires)
    }

    init().catch(err => console.error('Player init failed:', err))
  </script>
</body>
</html>
```

**dash.js**

For MPEG-DASH streams, [dash.js](https://github.com/Dash-IF/dash.js) provides a `RequestModifier` extension. The same token-rewriting logic as hls.js applies — only the manifest suffix differs.

The demo token API returns HLS URLs ending in `master.m3u8`. For DASH, replace that suffix with `index.mpd` — the token and expires values embedded in the path are the same for both formats.

```html theme={null}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
  <video id="video" controls style="width:854px; height:480px; background:#000;"></video>

  <script src="https://cdn.dashjs.org/latest/dash.all.min.js"></script>
  <script>
    // Replace with your authenticated backend endpoint.
    // Must return: { token, token_ip, client_ip, expires, url, url_ip }
    const TOKEN_API_URL =
      'https://video-token-102748.fastedge.app/?video=iKbrdNMcS9ylGuw&type=vod&expire=20'

    const tokenState = { token: '', expires: 0 }

    async function fetchToken() {
      const res = await fetch(TOKEN_API_URL)
      if (!res.ok) throw new Error(`Token API returned ${res.status}`)
      return res.json()
    }

    const TOKEN_RE = /\/([A-Za-z0-9_-]{6,})\/(1\d{9,})\//

    function rewriteUrl(url) {
      return url.replace(TOKEN_RE, `/${tokenState.token}/${tokenState.expires}/`)
    }

    const LEAD_SECONDS = 10
    let refreshTimer = null

    function scheduleRefresh(expires) {
      clearTimeout(refreshTimer)
      const msUntilRefresh = Math.max(1000, (expires - Date.now() / 1000 - LEAD_SECONDS) * 1000)
      refreshTimer = setTimeout(doRefresh, msUntilRefresh)
    }

    async function doRefresh() {
      try {
        const data = await fetchToken()
        // Use data.token_ip for IP-bound mode.
        tokenState.token   = data.token
        tokenState.expires = data.expires
        console.log('Token refreshed — next expiry:', new Date(data.expires * 1000))
        scheduleRefresh(data.expires)
      } catch (err) {
        console.error('Token refresh failed:', err)
        setTimeout(doRefresh, 5000)
      }
    }

    async function init() {
      const data = await fetchToken()

      // Seed state BEFORE initialize() so the first MPD request is rewritten.
      tokenState.token   = data.token    // or data.token_ip for IP-bound
      tokenState.expires = data.expires

      const player = dashjs.MediaPlayer().create()

      // Register RequestModifier BEFORE initialize() so it intercepts the first MPD fetch.
      player.extend('RequestModifier', function() {
        return {
          modifyRequest(config) {
            if (config.url) config.url = rewriteUrl(config.url)
            return config
          }
        }
      }, true)

      // Convert HLS URL to DASH by replacing the manifest suffix.
      const dashUrl = data.url.replace('master.m3u8', 'index.mpd')
      player.initialize(document.getElementById('video'), dashUrl, true)

      scheduleRefresh(data.expires)
    }

    init().catch(err => console.error('Player init failed:', err))
  </script>
</body>
</html>
```
