flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
165.01k stars 27.19k forks source link

[video_player] Example code occurd Bad state: No element error #142473

Closed ken-ty closed 7 months ago

ken-ty commented 7 months ago

Steps to reproduce

  1. Clone flutter/packages
  2. Run video_player/video_player/example/lib/main.dart
  3. Screen transition from the arrow button at the top right of the tab screen
  4. Occurs after the video ends.

Expected results

The screen pops and transitions to the original tab screen.

Actual results

The context cannot be obtained, and if you are using Android, the screen will freeze, or if you are using iPhone, the screen will be black.

Code sample

This code has not been edited.

Code sample ```dart // 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. // ignore_for_file: public_member_api_docs /// An example of using the plugin, controlling lifecycle and playback of the /// video. library; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; void main() { runApp( MaterialApp( home: _App(), ), ); } class _App extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( key: const ValueKey('home_page'), appBar: AppBar( title: const Text('Video player example'), actions: [ IconButton( key: const ValueKey('push_tab'), icon: const Icon(Icons.navigation), onPressed: () { Navigator.push<_PlayerVideoAndPopPage>( context, MaterialPageRoute<_PlayerVideoAndPopPage>( builder: (BuildContext context) => _PlayerVideoAndPopPage(), ), ); }, ) ], bottom: const TabBar( isScrollable: true, tabs: [ Tab( icon: Icon(Icons.cloud), text: 'Remote', ), Tab(icon: Icon(Icons.insert_drive_file), text: 'Asset'), Tab(icon: Icon(Icons.list), text: 'List example'), ], ), ), body: TabBarView( children: [ _BumbleBeeRemoteVideo(), _ButterFlyAssetVideo(), _ButterFlyAssetVideoInList(), ], ), ), ); } } class _ButterFlyAssetVideoInList extends StatelessWidget { @override Widget build(BuildContext context) { return ListView( children: [ const _ExampleCard(title: 'Item a'), const _ExampleCard(title: 'Item b'), const _ExampleCard(title: 'Item c'), const _ExampleCard(title: 'Item d'), const _ExampleCard(title: 'Item e'), const _ExampleCard(title: 'Item f'), const _ExampleCard(title: 'Item g'), Card( child: Column(children: [ Column( children: [ const ListTile( leading: Icon(Icons.cake), title: Text('Video video'), ), Stack( alignment: FractionalOffset.bottomRight + const FractionalOffset(-0.1, -0.1), children: [ _ButterFlyAssetVideo(), Image.asset('assets/flutter-mark-square-64.png'), ]), ], ), ])), const _ExampleCard(title: 'Item h'), const _ExampleCard(title: 'Item i'), const _ExampleCard(title: 'Item j'), const _ExampleCard(title: 'Item k'), const _ExampleCard(title: 'Item l'), ], ); } } /// A filler card to show the video in a list of scrolling contents. class _ExampleCard extends StatelessWidget { const _ExampleCard({required this.title}); final String title; @override Widget build(BuildContext context) { return Card( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.airline_seat_flat_angled), title: Text(title), ), Padding( padding: const EdgeInsets.all(8.0), child: OverflowBar( alignment: MainAxisAlignment.end, spacing: 8.0, children: [ TextButton( child: const Text('BUY TICKETS'), onPressed: () { /* ... */ }, ), TextButton( child: const Text('SELL TICKETS'), onPressed: () { /* ... */ }, ), ], ), ), ], ), ); } } class _ButterFlyAssetVideo extends StatefulWidget { @override _ButterFlyAssetVideoState createState() => _ButterFlyAssetVideoState(); } class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); _controller.addListener(() { setState(() {}); }); _controller.setLooping(true); _controller.initialize().then((_) => setState(() {})); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container( padding: const EdgeInsets.only(top: 20.0), ), const Text('With assets mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller, allowScrubbing: true), ], ), ), ), ], ), ); } } class _BumbleBeeRemoteVideo extends StatefulWidget { @override _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); } class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { late VideoPlayerController _controller; Future _loadCaptions() async { final String fileContents = await DefaultAssetBundle.of(context) .loadString('assets/bumble_bee_captions.vtt'); return WebVTTCaptionFile( fileContents); // For vtt files, use WebVTTCaptionFile } @override void initState() { super.initState(); _controller = VideoPlayerController.networkUrl( Uri.parse( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'), closedCaptionFile: _loadCaptions(), videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), ); _controller.addListener(() { setState(() {}); }); _controller.setLooping(true); _controller.initialize(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container(padding: const EdgeInsets.only(top: 20.0)), const Text('With remote mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), ClosedCaption(text: _controller.value.caption.text), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller, allowScrubbing: true), ], ), ), ), ], ), ); } } class _ControlsOverlay extends StatelessWidget { const _ControlsOverlay({required this.controller}); static const List _exampleCaptionOffsets = [ Duration(seconds: -10), Duration(seconds: -3), Duration(seconds: -1, milliseconds: -500), Duration(milliseconds: -250), Duration.zero, Duration(milliseconds: 250), Duration(seconds: 1, milliseconds: 500), Duration(seconds: 3), Duration(seconds: 10), ]; static const List _examplePlaybackRates = [ 0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0, ]; final VideoPlayerController controller; @override Widget build(BuildContext context) { return Stack( children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 50), reverseDuration: const Duration(milliseconds: 200), child: controller.value.isPlaying ? const SizedBox.shrink() : const ColoredBox( color: Colors.black26, child: Center( child: Icon( Icons.play_arrow, color: Colors.white, size: 100.0, semanticLabel: 'Play', ), ), ), ), GestureDetector( onTap: () { controller.value.isPlaying ? controller.pause() : controller.play(); }, ), Align( alignment: Alignment.topLeft, child: PopupMenuButton( initialValue: controller.value.captionOffset, tooltip: 'Caption Offset', onSelected: (Duration delay) { controller.setCaptionOffset(delay); }, itemBuilder: (BuildContext context) { return >[ for (final Duration offsetDuration in _exampleCaptionOffsets) PopupMenuItem( value: offsetDuration, child: Text('${offsetDuration.inMilliseconds}ms'), ) ]; }, child: Padding( padding: const EdgeInsets.symmetric( // Using less vertical padding as the text is also longer // horizontally, so it feels like it would need more spacing // horizontally (matching the aspect ratio of the video). vertical: 12, horizontal: 16, ), child: Text('${controller.value.captionOffset.inMilliseconds}ms'), ), ), ), Align( alignment: Alignment.topRight, child: PopupMenuButton( initialValue: controller.value.playbackSpeed, tooltip: 'Playback speed', onSelected: (double speed) { controller.setPlaybackSpeed(speed); }, itemBuilder: (BuildContext context) { return >[ for (final double speed in _examplePlaybackRates) PopupMenuItem( value: speed, child: Text('${speed}x'), ) ]; }, child: Padding( padding: const EdgeInsets.symmetric( // Using less vertical padding as the text is also longer // horizontally, so it feels like it would need more spacing // horizontally (matching the aspect ratio of the video). vertical: 12, horizontal: 16, ), child: Text('${controller.value.playbackSpeed}x'), ), ), ), ], ); } } class _PlayerVideoAndPopPage extends StatefulWidget { @override _PlayerVideoAndPopPageState createState() => _PlayerVideoAndPopPageState(); } class _PlayerVideoAndPopPageState extends State<_PlayerVideoAndPopPage> { late VideoPlayerController _videoPlayerController; bool startedPlaying = false; @override void initState() { super.initState(); _videoPlayerController = VideoPlayerController.asset('assets/Butterfly-209.mp4'); _videoPlayerController.addListener(() { if (startedPlaying && !_videoPlayerController.value.isPlaying) { Navigator.pop(context); } }); } @override void dispose() { _videoPlayerController.dispose(); super.dispose(); } Future started() async { await _videoPlayerController.initialize(); await _videoPlayerController.play(); startedPlaying = true; return true; } @override Widget build(BuildContext context) { return Material( child: Center( child: FutureBuilder( future: started(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.data ?? false) { return AspectRatio( aspectRatio: _videoPlayerController.value.aspectRatio, child: VideoPlayer(_videoPlayerController), ); } else { return const Text('waiting for video to load'); } }, ), ), ); } } ```

Screenshots or Video

Screenshots / Video demonstration https://github.com/flutter/flutter/assets/38717219/c6eca1e5-5400-413c-989d-af484a8b1960

Logs

Logs ```console ════════ Exception caught by foundation library ════════════════════════════════ The following StateError was thrown while dispatching notifications for VideoPlayerController: Bad state: No element When the exception was thrown, this was the stack: #0 Iterable.lastWhere (dart:core/iterable.dart:733:9) iterable.dart:733 #1 NavigatorState.pop (package:flutter/src/widgets/navigator.dart:5243:40) navigator.dart:5243 #2 Navigator.pop (package:flutter/src/widgets/navigator.dart:2569:27) navigator.dart:2569 #3 _PlayerVideoAndPopPageState.initState. (package:video_player_example/main.dart:407:19) main.dart:407 #4 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:433:24) change_notifier.dart:433 #5 ValueNotifier.value= (package:flutter/src/foundation/change_notifier.dart:555:5) change_notifier.dart:555 #6 VideoPlayerController.initialize.eventListener (package:video_player/video_player.dart:468:11) video_player.dart:468 (elided 13 frames from dart:async) The VideoPlayerController sending notification was: VideoPlayerController#cbd23(VideoPlayerValue(duration: 0:00:07.540000, size: Size(1280.0, 720.0), position: 0:00:07.306000, caption: Caption(number: 0, start: 0:00:00.000000, end: 0:00:00.000000, text: ), captionOffset: 0:00:00.000000, buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:07.540000)], isInitialized: true, isPlaying: false, isLooping: false, isBuffering: true, volume: 1.0, playbackSpeed: 1.0, errorDescription: null, isCompleted: true),) ════════════════════════════════════════════════════════════════════════════════ ════════ Exception caught by foundation library ════════════════════════════════ 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 5238 pos 12: '!_debugLocked': is not true. ════════════════════════════════════════════════════════════════════════════════ ════════ Exception caught by foundation library ════════════════════════════════ 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 5238 pos 12: '!_debugLocked': is not true. ════════════════════════════════════════════════════════════════════════════════ ════════ Exception caught by foundation library ════════════════════════════════ 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 5238 pos 12: '!_debugLocked': is not true. ════════════════════════════════════════════════════════════════════════════════ ════════ Exception caught by widgets library ═══════════════════════════════════ 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 5495 pos 12: '!_debugLocked': is not true. The relevant error-causing widget was: ════════════════════════════════════════════════════════════════════════════════ ```

Flutter Doctor output

Doctor output ```console xxx@yyy packages % flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.16.7, on macOS 14.0 23A344 darwin-x64, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 15.0) [✓] Chrome - develop for the web [✓] Android Studio (version 2021.3) [✓] VS Code (version 1.85.2) [✓] Connected device (5 available) [✓] Network resources • No issues found! ```
ken-ty commented 7 months ago

I opened an issue related to Flutter/Flutter for the first time. Is this reproducible? I'd like to write a PR if you need a fix. 👍

ken-ty commented 7 months ago

After doing some research, I found the cause. The original condition calls Navigator.pop() multiple times each time the controller changes. You can avoid this problem by modifying the conditions appropriately as shown below.

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

    _videoPlayerController =
        VideoPlayerController.asset('assets/Butterfly-209.mp4');
    _videoPlayerController.addListener(() {
      // # before
      // if (startedPlaying && !_videoPlayerController.value.isPlaying) {
      //   Navigator.pop(context);
      // }
      // # after
      if (startedPlaying && _videoPlayerController.value.isCompleted) {
        Navigator.pop(context);
        startedPlaying = false;
      }
    });
  }
  ...
huycozy commented 7 months ago

Thanks for filing the issue and proposing a solution for this. But I see there is a known issue at https://github.com/flutter/flutter/issues/122690. We welcome everyone to contribute a PR so please take it if you are interested :)

Closing this as a duplicate of https://github.com/flutter/flutter/issues/122690.

github-actions[bot] commented 7 months ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.