-
Notifications
You must be signed in to change notification settings - Fork 3.4k
[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
nateshmbhat
wants to merge
17
commits into
flutter:main
Choose a base branch
from
nateshmbhat:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+3,526
−378
Open
Changes from 3 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
c331d42
feat(video_player): add audio track metadata support with bitrate, sa…
nateshmbhat 870b702
feat(video): implement audio track retrieval for HLS streams in iOS
nateshmbhat 3435c20
test(video_player): update test cases
nateshmbhat 4deae93
refactor(video_player): move audio track formatting from model to UI …
nateshmbhat 86ba273
chore(video_player): updated pubspec
nateshmbhat 5e09464
Merge branch 'main' into main
nateshmbhat 6bab30b
feat(video_player): add audio track retrieval functionality and tests
nateshmbhat 2dec18d
Merge branch 'main' of https://github.com/flutter/packages
nateshmbhat d29ad6c
Merge branch 'main' of github.com:nateshmbhat/flutter_packages
nateshmbhat cbb854b
feat(video_player): updated dependencies in pubspec to path based on …
nateshmbhat c7488c1
Merge branch 'main' into main
nateshmbhat 567925d
fix(video_player): fix code for failing ci/cd checks
nateshmbhat a3a0884
Merge branch 'main' of github.com:nateshmbhat/flutter_packages
nateshmbhat 1dfbf82
fix(video_player_web): reverted changes in video_player_web
nateshmbhat 638bf43
feat(video_player): ios code refactor and added tests
nateshmbhat e507002
fix: updated ios tests
nateshmbhat d7aa9fb
fix(ios): updated method name for getAudioTracks
nateshmbhat File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
211 changes: 211 additions & 0 deletions
211
packages/video_player/video_player/example/lib/audio_tracks_demo.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
// 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 { | ||
const AudioTracksDemo({super.key}); | ||
|
||
@override | ||
State<AudioTracksDemo> createState() => _AudioTracksDemoState(); | ||
} | ||
|
||
class _AudioTracksDemoState extends State<AudioTracksDemo> { | ||
VideoPlayerController? _controller; | ||
List<VideoAudioTrack> _audioTracks = []; | ||
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(() {}); | ||
|
||
// 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 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: [ | ||
// 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: [ | ||
IconButton( | ||
onPressed: () { | ||
setState(() { | ||
_controller!.value.isPlaying | ||
? _controller!.pause() | ||
: _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: [ | ||
Row( | ||
children: [ | ||
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: (context, index) { | ||
final 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: [ | ||
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, | ||
), | ||
); | ||
}, | ||
), | ||
), | ||
], | ||
), | ||
), | ||
), | ||
], | ||
), | ||
); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
_getAudioTracks
method can be simplified to improve readability and ensuresetState
is only called once after the asynchronous operation completes. This also makes it easier to handle the widget's lifecycle by checkingmounted
once before updating the state.