api.video-flutter-player
api.video-flutter-player copied to clipboard
[Bug]: player analytics crash the app
Version
1.4.0
Environment that reproduces the issue
- Device: iPhone SE
- iOS version: 18.0
- only in release mode
- only on a physical device
- The issue doesn't exist on Android devices
Is it reproducible in the example application?
Not tested
Reproduction steps
We have an app that uses TikTok-like reel scrolling, where two video controllers can exist at the same time (one for the reel that is being scrolled out, one for the reel that is being scrolled in). After scrolling a few reels, the app crashes (completely exits). This can only be observed on iPhones - the app works fine on Android. The video controllers are disposed properly. The (simplified) code that we use is given below:
class ReelPage extends StatefulWidget {
const ReelPage(this.args, {super.key});
final ReelPageData args;
@override
State<ReelPage> createState() => _ReelPageState();
}
class _ReelPageState extends State<ReelPage> {
late final PageController _pageController;
@override
void initState() {
super.initState();
_pageController = PageController(initialPage: widget.args.initialIndex, keepPage: false);
}
final Map<int, Future<VideoAssets>> _cachedAssets = {};
Future<VideoAssets> _fetchVideoAssets(final int index) async {
if (_cachedAssets.containsKey(index)) {
return _cachedAssets[index]!;
}
_cachedAssets[index] = _reelService
.getReels(locale: context.locale, page: index + 1, perPage: 1)
.then((final reel) => _getVideoAssetsForPost(reel.single));
return _cachedAssets[index]!;
}
final _videoService = inject<VideoService>();
final _reelService = inject<ReelService>();
Future<VideoAssets> _getVideoAssetsForPost(final Post reel) => _videoService.getVideoAssets(reel.id);
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(final BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: PageView.builder(
scrollDirection: Axis.vertical,
pageSnapping: true,
controller: _pageController,
itemBuilder: (final context, final index) => _ReelView(_fetchVideoAssets(index), _pageController),
),
);
}
}
class _ReelView extends StatefulWidget {
_ReelView(this._videoAssets, this._pageController) : super(key: ValueKey(_videoAssets.hashCode));
final Future<VideoAssets> _videoAssets;
final PageController _pageController;
@override
State<_ReelView> createState() => __ReelViewState();
}
class __ReelViewState extends State<_ReelView> {
LoadingStatus _status = LoadingStatus.loading;
ApiVideoPlayerController? _videoPlayerController;
Uri? _playerUri;
bool _showWebPlayer = false;
@override
void initState() {
super.initState();
_setUpPlayer();
}
@override
void dispose() {
_videoPlayerController?.pause().then((_) => _videoPlayerController?.dispose());
super.dispose();
}
Future<void> _setUpPlayer() async {
if (widget._pageController.page == null) return;
setState(() {
_status = LoadingStatus.loading;
});
try {
final videoAssets = await widget._videoAssets;
if (!mounted) return;
_videoPlayerController =
ApiVideoPlayerController(videoOptions: VideoOptions(videoId: videoAssets.player.pathSegments.last));
if (!mounted) return;
await _videoPlayerController!.initialize();
if (!mounted) return;
setState(() {
_status = LoadingStatus.loaded;
_videoPlayerController?.play();
_videoPlayerController?.setIsLooping(true);
});
} catch (e, st) {
inject<ErrorService>().recordError(e, st);
if (mounted) {
setState(() {
_status = LoadingStatus.error;
});
}
}
}
@override
Widget build(final BuildContext context) {
final loc = context.loc;
return switch (_status) {
LoadingStatus.loading => const Center(child: CircularProgressIndicator()),
LoadingStatus.error => Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
loc.errorOccured,
style: const TextStyle(fontSize: 18, color: Colors.white),
),
const Gap(8),
TextButton(
onPressed: _setUpPlayer,
child: Text(
loc.tryAgain,
style: const TextStyle(fontFamily: 'Rajdhani', fontSize: 16, fontWeight: FontWeight.bold),
),
),
],
),
),
LoadingStatus.loaded => Stack(
fit: StackFit.expand,
children: [
Center(
child: AspectRatio(
aspectRatio: 9 / 16,
child: _showWebPlayer
? WebPlayer(playerUri: _playerUri!, autoPlay: true)
: ApiVideoPlayer.noControls(controller: _videoPlayerController!),
),
),
SafeArea(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 24),
child: _BottomControls(_videoPlayerController!),
),
),
),
],
),
};
}
}
Expected result
The app doesn't crash
Actual result
The app crashes with the following stack trace:
Additional context
No response
Relevant logs output