Skip to content
AB

Keeping Authenticated Media Playing in Flutter

Handle expiring tokens, range requests, background playback, source rebinding, and logout for protected Flutter audio.

Ashish BhakhandPublished

Protected audio can start correctly and fail forty minutes later.

The first request used a valid token. During long playback, the player makes more requests for media ranges. If the token expires, buffering begins and the UI may have no useful error.

Authenticated media combines two lifecycles that rarely end together:

  • the playback session;
  • the authentication token.

Refresh before playback begins#

Do not start a long session with a token near expiry:

Code
Future<AudioSource> createProtectedSource(
  String url,
  TokenProvider tokens,
) async {
  final token = await tokens.refreshAccessToken();

  return AudioSource.uri(
    Uri.parse(url),
    headers: {
      'authorization': 'Bearer ' + token,
    },
  );
}

Some servers or content delivery networks accept signed query parameters instead of headers. Use the mechanism your backend supports and avoid logging the resulting URL.

Refresh during long sessions#

Schedule refresh before the expected token expiry:

Code
class MediaAuthSession {
  MediaAuthSession({
    required this.refresh,
    required this.onTokenChanged,
  });

  final Future<String> Function() refresh;
  final Future<void> Function(String token) onTokenChanged;
  Timer? _timer;
  bool _refreshing = false;

  void start() {
    _timer ??= Timer.periodic(
      const Duration(minutes: 30),
      (_) => unawaited(_refreshSafely()),
    );
  }

  Future<void> _refreshSafely() async {
    if (_refreshing) return;
    _refreshing = true;

    try {
      final token = await refresh();
      await onTokenChanged(token);
    } finally {
      _refreshing = false;
    }
  }

  void stop() {
    _timer?.cancel();
    _timer = null;
  }
}

The exact interval should come from the authentication system. Refresh early rather than waiting for playback to fail.

Rebind without losing position#

When credentials change, recreate the source while preserving the session:

Code
Future<void> rebindSource(String token) async {
  final position = player.position;
  final wasPlaying = player.playing;
  final speed = player.speed;

  await player.setAudioSource(
    sourceWithToken(token),
    initialPosition: position,
  );
  await player.setSpeed(speed);

  if (wasPlaying) {
    await player.play();
  }
}

Guard against two refreshes rebinding simultaneously. Preserve the media item and system playback state as well as player position.

Rebinding may create a small interruption. If the server supports another strategy, such as longer-lived signed media URLs, compare its security and playback tradeoffs.

Keep token logic outside widgets#

The player page should not read secure storage or run refresh timers. Put token work in an authentication service and the media-session integration near the audio handler.

This is especially important for background playback. A widget may be disposed while the system media session continues.

Handle failures without destroying playback#

A temporary refresh failure should not immediately stop already-buffered audio. Keep the current source, report diagnostics, and retry before the token is expected to expire.

If authorization is definitively revoked:

  • stop protected playback;
  • clear the media item;
  • remove protected cached files if policy requires it;
  • send the user through the normal authentication flow.

Never retry unauthorized range requests forever.

Treat logout as a hard boundary#

Logout should stop the media auth timer and protected playback before tokens are cleared:

Code
Future<void> logout() async {
  mediaAuthSession.stop();
  await audioController.stop();
  await tokenStore.clear();
}

This ordering avoids a background handler attempting to refresh with a session that no longer exists.

Test with short token lifetimes#

In test or staging, reduce the lifetime so these cases are practical:

  • token expires while playing;
  • refresh succeeds and position is preserved;
  • refresh temporarily fails;
  • two refresh triggers overlap;
  • user pauses during rebinding;
  • logout occurs during playback;
  • the app is backgrounded during refresh;
  • a range request receives 401.

just_audio provides the player and source APIs; the application still owns credential lifecycle. Protected media becomes reliable only when token refresh is designed as part of playback rather than as an HTTP concern that ended when the first request succeeded.

← Back to all writing