Skip to content

[video_player] Add audio track metadata support (bitrate, sample rate, channels, codec) #9782

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/video_player/video_player/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.11.0

* Adds audio track metadata support including bitrate, sample rate, channel count, and codec information.

## 2.10.0

* Adds support for platform views as an optional way of displaying a video on Android and iOS.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
A3677D96C5C9245FC9DDA03F /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -236,6 +237,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
A3677D96C5C9245FC9DDA03F /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A526C4C26D549003F5EB64A6 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

/// Demo page showing how to retrieve and display available audio tracks
class AudioTracksDemo extends StatefulWidget {
/// Creates an AudioTracksDemo widget.
const AudioTracksDemo({super.key});

@override
State<AudioTracksDemo> createState() => _AudioTracksDemoState();
}

class _AudioTracksDemoState extends State<AudioTracksDemo> {
VideoPlayerController? _controller;
List<VideoAudioTrack> _audioTracks = <VideoAudioTrack>[];
bool _isLoading = false;

@override
void initState() {
super.initState();
_initializeVideoPlayer();
}

Future<void> _initializeVideoPlayer() async {
// Apple's test HLS stream with multiple audio tracks
const String videoUrl =
'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8';

_controller = VideoPlayerController.networkUrl(Uri.parse(videoUrl));

try {
await _controller!.initialize();
setState(() {
// Video initialized
});

// Get audio tracks after initialization
await _getAudioTracks();
} catch (e) {
debugPrint('Error initializing video player: $e');
}
}

Future<void> _getAudioTracks() async {
if (_controller == null) {
return;
}

setState(() {
_isLoading = true;
});

try {
final List<VideoAudioTrack> tracks = await _controller!.getAudioTracks();
setState(() {
_audioTracks = tracks;
_isLoading = false;
});
} catch (e) {
debugPrint('Error getting audio tracks: $e');
setState(() {
_isLoading = false;
});
}
}

@override
void dispose() {
_controller?.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Audio Tracks Demo'),
backgroundColor: Colors.blue,
),
body: Column(
children: <Widget>[
// Video Player
if (_controller != null && _controller!.value.isInitialized)
AspectRatio(
aspectRatio: _controller!.value.aspectRatio,
child: VideoPlayer(_controller!),
)
else
const SizedBox(
height: 200,
child: Center(
child: CircularProgressIndicator(),
),
),

// Video Controls
if (_controller != null && _controller!.value.isInitialized)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () {
setState(() {
if (_controller!.value.isPlaying) {
_controller!.pause();
} else {
_controller!.play();
}
});
},
icon: Icon(
_controller!.value.isPlaying
? Icons.pause
: Icons.play_arrow,
),
),
IconButton(
onPressed: _getAudioTracks,
icon: const Icon(Icons.refresh),
tooltip: 'Refresh Audio Tracks',
),
],
),

const Divider(),

// Audio Tracks Section
Expanded(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
const Text(
'Available Audio Tracks:',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
if (_isLoading)
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
],
),
const SizedBox(height: 16),
if (_audioTracks.isEmpty && !_isLoading)
const Text(
'No audio tracks found or video not initialized.',
style: TextStyle(color: Colors.grey),
)
else
Expanded(
child: ListView.builder(
itemCount: _audioTracks.length,
itemBuilder: (BuildContext context, int index) {
final VideoAudioTrack track = _audioTracks[index];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: track.isSelected
? Colors.green
: Colors.grey,
child: Icon(
track.isSelected
? Icons.check
: Icons.audiotrack,
color: Colors.white,
),
),
title: Text(
track.label,
style: TextStyle(
fontWeight: track.isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('ID: ${track.id}'),
Text('Language: ${track.language}'),
],
),
trailing: track.isSelected
? const Chip(
label: Text('Selected'),
backgroundColor: Colors.green,
labelStyle:
TextStyle(color: Colors.white),
)
: null,
),
);
},
),
),
],
),
),
),
],
),
);
}
}
Loading