Search code examples
flutterdartvideolocal-storageout-of-memory

Playing video list from local storage in Flutter (out of memory error)


The goal

I'd like to download a list of videos to local storage and then autoplay them in Flutter on Android.

The problem

  • I can do it with the video_player package and one VideoPlayerController at a time, but there are pauses between videos when I am initializing a new controller.
  • If I use two or more controllers, I get an OutOfMemoryError from Flutter and the app crashes on the third video.

Example code

Here is an example project to reproduce the problem. Since it's hard to find a good working link to videos to download, this example will simulate the download by putting the videos in assets and then copying them to the application support directory.

If you need some videos to download and put in the assets folder, use these from Pixibay:

Add the following dependencies to pubspec.yaml:

dependencies:
  video_player: ^2.7.0
  path_provider: ^2.1.0
  path: ^1.8.3

Create a file named manager.dart in your lib folder and paste in the following code:

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';

class AppManager {
  final isDownloadingNotifier = ValueNotifier<bool>(true);
  late List<File> videoFiles;

  Future<void> init() async {
    await _copyFiles(); // can comment this line out if already copied
    videoFiles = await _getDownloadedFiles();
    isDownloadingNotifier.value = false;
  }

  // simulate downloading files by copying from assets
  Future<void> _copyFiles() async {
    final assetFiles = [
      'assets/video01.mp4',
      'assets/video02.mp4',
      'assets/video03.mp4',
      'assets/video04.mp4',
    ];

    final directory = await getApplicationSupportDirectory();

    for (var assetPath in assetFiles) {
      print('loading $assetPath');
      final data = await rootBundle.load(assetPath);
      final bytes = data.buffer.asUint8List();
      final filename = basename(assetPath);
      final filePath = join(directory.path, filename);
      final file = File(filePath);
      await file.writeAsBytes(bytes, flush: true);
      print('saved $filename');
    }
  }

  Future<List<File>> _getDownloadedFiles() async {
    final List<File> files = [];
    final directory = await getApplicationSupportDirectory();
    await for (var entity in directory.list()) {
      if (entity is File && entity.path.endsWith('.mp4')) {
        files.add(entity);
      }
    }
    files.forEach((file) => print(file.path));
    return files;
  }
}

Then replace main.dart with the following code. (This code comes primarily from this Stack Overflow answer, which works for me when playing the video list from the network rather than from local storage.)

import 'dart:io';

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

final manager = AppManager();

main() {
  WidgetsFlutterBinding.ensureInitialized();
  manager.init();
  runApp(
    MaterialApp(
      home: ValueListenableBuilder<bool>(
        valueListenable: manager.isDownloadingNotifier,
        builder: (context, isDownloading, child) {
          if (isDownloading) {
            return const DownloadingPlaceholder();
          }
          return VideoPlayerDemo(videoFiles: manager.videoFiles);
        },
      ),
    ),
  );
}

class DownloadingPlaceholder extends StatefulWidget {
  const DownloadingPlaceholder({super.key});

  @override
  State<DownloadingPlaceholder> createState() => _DownloadingPlaceholderState();
}

class _DownloadingPlaceholderState extends State<DownloadingPlaceholder> {
  @override
  Widget build(BuildContext context) {
    return const Center(child: CircularProgressIndicator());
  }
}

class VideoPlayerDemo extends StatefulWidget {
  const VideoPlayerDemo({
    super.key,
    required this.videoFiles,
  });

  final List<File> videoFiles;

  @override
  State<VideoPlayerDemo> createState() => _VideoPlayerDemoState();
}

class _VideoPlayerDemoState extends State<VideoPlayerDemo> {
  int index = 0;
  double _position = 0;
  double _buffer = 0;
  bool _lock = true;
  final Map<String, VideoPlayerController> _controllers = {};
  final Map<int, VoidCallback> _listeners = {};
  late final _files = widget.videoFiles;

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

    if (_files.isNotEmpty) {
      _initController(0).then((_) {
        _playController(0);
      });
    }

    if (_files.length > 1) {
      _initController(1).whenComplete(() => _lock = false);
    }
  }

  VoidCallback _listenerSpawner(index) {
    return () {
      int dur = _controller(index).value.duration.inMilliseconds;
      int pos = _controller(index).value.position.inMilliseconds;
      int buf = _controller(index).value.buffered.last.end.inMilliseconds;

      setState(() {
        if (dur <= pos) {
          _position = 0;
          return;
        }
        _position = pos / dur;
        _buffer = buf / dur;
      });
      if (dur - pos < 1) {
        if (index < _files.length - 1) {
          _nextVideo();
        }
      }
    };
  }

  VideoPlayerController _controller(int index) {
    final path = _files.elementAt(index).path;
    return _controllers[path]!;
  }

  Future<void> _initController(int index) async {
    final file = _files.elementAt(index);
    var controller = VideoPlayerController.file(file);
    final path = _files.elementAt(index).path;
    _controllers[path] = controller;
    await controller.initialize();
  }

  void _removeController(int index) {
    _controller(index).dispose();
    final path = _files.elementAt(index).path;
    _controllers.remove(path);
    _listeners.remove(index);
  }

  void _stopController(int index) {
    _controller(index).removeListener(_listeners[index]!);
    _controller(index).pause();
    _controller(index).seekTo(const Duration(milliseconds: 0));
  }

  void _playController(int index) async {
    if (!_listeners.keys.contains(index)) {
      _listeners[index] = _listenerSpawner(index);
    }
    _controller(index).addListener(_listeners[index]!);
    await _controller(index).play();
    setState(() {});
  }

  void _previousVideo() {
    if (_lock || index == 0) {
      return;
    }
    _lock = true;

    _stopController(index);

    if (index + 1 < _files.length) {
      _removeController(index + 1);
    }

    _playController(--index);

    if (index == 0) {
      _lock = false;
    } else {
      _initController(index - 1).whenComplete(() => _lock = false);
    }
  }

  void _nextVideo() async {
    if (_lock || index == _files.length - 1) {
      return;
    }
    _lock = true;

    _stopController(index);

    if (index - 1 >= 0) {
      _removeController(index - 1);
    }

    _playController(++index);

    if (index == _files.length - 1) {
      _lock = false;
    } else {
      _initController(index + 1).whenComplete(() => _lock = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Playing ${index + 1} of ${_files.length}"),
      ),
      body: Stack(
        children: <Widget>[
          GestureDetector(
            onLongPressStart: (_) => _controller(index).pause(),
            onLongPressEnd: (_) => _controller(index).play(),
            child: Center(
              child: AspectRatio(
                aspectRatio: _controller(index).value.aspectRatio,
                child: Center(child: VideoPlayer(_controller(index))),
              ),
            ),
          ),
          Positioned(
            child: Container(
              height: 10,
              width: MediaQuery.of(context).size.width * _buffer,
              color: Colors.grey,
            ),
          ),
          Positioned(
            child: Container(
              height: 10,
              width: MediaQuery.of(context).size.width * _position,
              color: Colors.greenAccent,
            ),
          ),
        ],
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: _previousVideo,
            child: const Icon(Icons.arrow_back),
          ),
          const SizedBox(width: 24),
          FloatingActionButton(
            onPressed: _nextVideo,
            child: const Icon(Icons.arrow_forward),
          ),
        ],
      ),
    );
  }
}

Running the example

When I run the code on an Android emulator (Android API 34 x86_64 running on macOS), I get the following log output (truncated due to Stack Overflow question lenth limits), which ends with an OutOfMemoryError after the second video finishes playing:

Launching lib/main.dart on sdk gphone64 x86 64 in debug mode...
✓  Built build/app/outputs/flutter-apk/app-debug.apk.
Connecting to VM Service at ws://127.0.0.1:65153/ezV68AQSK94=/ws
I/flutter (13128): loading assets/video01.mp4
I/flutter (13128): saved video01.mp4
I/flutter (13128): loading assets/video02.mp4
D/EGL_emulation(13128): app_time_stats: avg=350.42ms min=10.67ms max=2356.31ms count=13
I/flutter (13128): saved video02.mp4
I/flutter (13128): loading assets/video03.mp4
D/EGL_emulation(13128): app_time_stats: avg=50.98ms min=15.60ms max=678.09ms count=23
I/flutter (13128): saved video03.mp4
I/flutter (13128): loading assets/video04.mp4
I/flutter (13128): saved video04.mp4
I/flutter (13128): /data/user/0/dev.suragch.flutter_playground/files/video01.mp4
I/flutter (13128): /data/user/0/dev.suragch.flutter_playground/files/video02.mp4
I/flutter (13128): /data/user/0/dev.suragch.flutter_playground/files/video03.mp4
I/flutter (13128): /data/user/0/dev.suragch.flutter_playground/files/video04.mp4
I/ExoPlayerImpl(13128): Init 4769990 [ExoPlayerLib/2.18.7] [emu64xa, sdk_gphone64_x86_64, Google, 34]
W/tter_playground(13128): Accessing hidden method Landroid/media/AudioTrack;->getLatency()I (unsupported, reflection, allowed)
I/Surface (13128): Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not SurfaceFlinger
I/ExoPlayerImpl(13128): Init 7570ff9 [ExoPlayerLib/2.18.7] [emu64xa, sdk_gphone64_x86_64, Google, 34]
...
D/CCodec  (13128): encoding statistics level = 0
D/CCodec  (13128): setup formats input: AMessage(what = 0x00000000) = {
D/CCodec  (13128):   int32_t height = 2160
D/CCodec  (13128):   int32_t level = 65536
D/CCodec  (13128):   int32_t max-input-size = 6291456
D/CCodec  (13128):   string mime = "video/avc"
D/CCodec  (13128):   int32_t profile = 65536
D/CCodec  (13128):   int32_t width = 3840
D/CCodec  (13128):   Rect crop(0, 0, 3839, 2159)
D/CCodec  (13128): }
D/CCodec  (13128): setup formats output: AMessage(what = 0x00000000) = {
D/CCodec  (13128):   int32_t android._color-format = 2135033992
D/CCodec  (13128):   int32_t android._video-scaling = 1
D/CCodec  (13128):   int32_t rotation-degrees = 0
D/CCodec  (13128):   int32_t color-standard = 6
D/CCodec  (13128):   int32_t color-range = 2
D/CCodec  (13128):   int32_t color-transfer = 3
D/CCodec  (13128):   int32_t sar-height = 1
D/CCodec  (13128):   int32_t sar-width = 1
D/CCodec  (13128):   Rect crop(0, 0, 3839, 2159)
D/CCodec  (13128):   int32_t width = 3840
D/CCodec  (13128):   int32_t height = 2160
D/CCodec  (13128):   int32_t max-height = 2160
D/CCodec  (13128):   int32_t max-width = 3840
D/CCodec  (13128):   string mime = "video/raw"
D/CCodec  (13128):   int32_t android._dataspace = 281411584
D/CCodec  (13128):   int32_t color-format = 2130708361
D/CCodec  (13128): }
I/CCodecConfig(13128): query failed after returning 12 values (BAD_INDEX)
W/Codec2Client(13128): query -- param skipped: index = 1342179345.
W/Codec2Client(13128): query -- param skipped: index = 2415921170.
W/Codec2Client(13128): query -- param skipped: index = 1610614798.
W/Codec2Client(13128): query -- param skipped: index = 2684356609.
D/C2Store (13128): Using DMABUF Heaps
D/MediaCodec(13128): keep callback message for reclaim
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#392] Created input block pool with allocatorID 16 => poolID 17 - OK (0)
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#392] Query output surface allocator returned 0 params => BAD_INDEX (6)
W/Codec2Client(13128): query -- param skipped: index = 1342179345.
W/Codec2Client(13128): query -- param skipped: index = 2415921170.
W/Codec2Client(13128): query -- param skipped: index = 1610614798.
W/Codec2Client(13128): query -- param skipped: index = 2684356609.
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Created input block pool with allocatorID 16 => poolID 18 - OK (0)
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Query output surface allocator returned 0 params => BAD_INDEX (6)
I/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Created output block pool with allocatorID 18 => poolID 57 - OK
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Configured output block pool ids 57 => OK
D/Codec2-OutputBufferQueue(13128): remote graphic buffer migration 0/0
D/Codec2Client(13128): setOutputSurface -- failed to set consumer usage (6/BAD_INDEX)
D/Codec2Client(13128): setOutputSurface -- generation=13443074 consumer usage=0x900
D/Codec2Client(13128): Surface configure completed
I/DMABUFHEAPS(13128): Using DMA-BUF heap named: system
I/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#392] Created output block pool with allocatorID 18 => poolID 56 - OK
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#392] Configured output block pool ids 56 => OK
D/Codec2-OutputBufferQueue(13128): remote graphic buffer migration 0/0
D/Codec2Client(13128): setOutputSurface -- failed to set consumer usage (6/BAD_INDEX)
D/Codec2Client(13128): setOutputSurface -- generation=13443073 consumer usage=0x900
D/Codec2Client(13128): Surface configure completed
D/MediaCodecInfo(13128): NoSupport [sizeAndRate.cover, [email protected]] [c2.goldfish.h264.decoder, video/avc] [emu64xa, sdk_gphone64_x86_64, Google, 34]
W/MediaCodecRenderer(13128): Format exceeds selected codec's capabilities [id=1, mimeType=video/avc, codecs=avc1.640034, res=3840x2160, fps=59.996605, c2.goldfish.h264.decoder]
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Ignoring stale input buffer done callback: last flush index = 0, frameIndex = 0
I/tter_playground(13128): Background concurrent copying GC freed 30030(2285KB) AllocSpace objects, 13(548KB) LOS objects, 49% free, 10MB/20MB, paused 902us,33us total 615.902ms
D/CCodecConfig(13128): c2 config diff is   c2::u32 raw.color.matrix = 1
D/CCodecConfig(13128):   c2::u32 raw.color.primaries = 1
D/CCodecConfig(13128):   c2::u32 raw.color.transfer = 3
D/CCodecConfig(13128):   c2::u32 raw.crop.height = 2160
D/CCodecConfig(13128):   c2::u32 raw.crop.left = 0
D/CCodecConfig(13128):   c2::u32 raw.crop.top = 0
D/CCodecConfig(13128):   c2::u32 raw.crop.width = 3840
D/CCodecBuffers(13128): [c2.goldfish.h264.decoder#392:2D-Output] popFromStashAndRegister: at 1000000000000us, output format changed to AMessage(what = 0x00000000) = {
D/CCodecBuffers(13128):   int32_t android._color-format = 2135033992
D/CCodecBuffers(13128):   int32_t android._video-scaling = 1
D/CCodecBuffers(13128):   int32_t rotation-degrees = 0
D/CCodecBuffers(13128):   int32_t color-standard = 1
D/CCodecBuffers(13128):   int32_t color-range = 2
D/CCodecBuffers(13128):   int32_t color-transfer = 3
D/CCodecBuffers(13128):   int32_t sar-height = 1
D/CCodecBuffers(13128):   int32_t sar-width = 1
D/CCodecBuffers(13128):   Rect crop(0, 0, 3839, 2159)
D/CCodecBuffers(13128):   int32_t width = 3840
D/CCodecBuffers(13128):   int32_t height = 2160
D/CCodecBuffers(13128):   int32_t max-height = 2160
D/CCodecBuffers(13128):   int32_t max-width = 3840
D/CCodecBuffers(13128):   string mime = "video/raw"
D/CCodecBuffers(13128):   int32_t android._dataspace = 260
D/CCodecBuffers(13128):   int32_t color-format = 2130708361
D/CCodecBuffers(13128): }
D/CCodecConfig(13128): c2 config diff is   c2::u32 raw.crop.height = 2160
D/CCodecConfig(13128):   c2::u32 raw.crop.left = 0
D/CCodecConfig(13128):   c2::u32 raw.crop.top = 0
D/CCodecConfig(13128):   c2::u32 raw.crop.width = 3840
W/tter_playground(13128): Long monitor contention with owner ExoPlayer:Loader:ProgressiveMediaPeriod (13186) at com.google.android.exoplayer2.upstream.Allocation com.google.android.exoplayer2.upstream.DefaultAllocator.allocate()(DefaultAllocator.java:110) waiters=0 in int com.google.android.exoplayer2.upstream.DefaultAllocator.getTotalBytesAllocated() for 262ms
I/tter_playground(13128): Background young concurrent copying GC freed 382(192KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 24MB/24MB, paused 79us,11us total 485.850ms
...
I/tter_playground(13128): Background concurrent copying GC freed 474(99KB) AllocSpace objects, 0(0B) LOS objects, 18% free, 109MB/133MB, paused 8.636ms,143us total 160.927ms

════════ 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      _Array.last (dart:core-patch/array.dart:56:5)
#1      _VideoPlayerDemoState._listenerSpawner.<anonymous closure>
#2      ChangeNotifier.notifyListeners
#3      ValueNotifier.value=
#4      VideoPlayerController.play
#5      _VideoPlayerDemoState._playController
#6      _VideoPlayerDemoState.initState.<anonymous closure>
<asynchronous suspension>
The VideoPlayerController sending notification was: VideoPlayerController#94dc3(VideoPlayerValue(duration: 0:00:12.713000, size: Size(3840.0, 2160.0), position: 0:00:00.000000, caption: Caption(number: 0, start: 0:00:00.000000, end: 0:00:00.000000, text: ), captionOffset: 0:00:00.000000, buffered: [], isInitialized: true, isPlaying: true, isLooping: false, isBuffering: false, volume: 1.0, playbackSpeed: 1.0, errorDescription: null))
════════════════════════════════════════════════════════════════════════════════
I/Surface (13128): Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not SurfaceFlinger
D/BufferPoolAccessor2.0(13128): bufferpool2 0x7fa797d7a9b8 : 5(31457280 size) total buffers - 5(31457280 size) used buffers - 20/25 (recycle/alloc) - 5/24 (fetch/transfer)
I/tter_playground(13128): Background young concurrent copying GC freed 514(115KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 135MB/135MB, paused 792us,11us total 152.842ms
...
W/tter_playground(13128):   native: #151 pc 003784c4  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+756) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #152 pc 003c535c  /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke+204) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #153 pc 0056ddb0  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+2080) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #154 pc 0039ae32  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+16546) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #155 pc 003937a5  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+5) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #156 pc 0050c774  /system/framework/framework.jar (com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run)
W/tter_playground(13128):   native: #157 pc 00565ff7  /apex/com.android.art/lib64/libart.so (art::interpreter::Execute +647) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #158 pc 0091efc5  /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+901) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #159 pc 00391bdc  /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+140) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #160 pc 00d1578e  /data/misc/apexdata/com.android.art/dalvik-cache/x86_64/boot.oat (com.android.internal.os.ZygoteInit.main+3038)
W/tter_playground(13128):   native: #161 pc 00378826  /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub+806) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #162 pc 003c538f  /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke+255) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #163 pc 007f123f  /apex/com.android.art/lib64/libart.so (art::JValue art::InvokeWithVarArgs<art::ArtMethod*>+399) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #164 pc 006a7e9c  /apex/com.android.art/lib64/libart.so (art::JNI<true>::CallStaticVoidMethodV+668) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128):   native: #165 pc 000de888  /system/lib64/libandroid_runtime.so (_JNIEnv::CallStaticVoidMethod+136) (BuildId: 140296c7da6bfd8e3c406c87d6aeb4e4)
W/tter_playground(13128):   native: #166 pc 000eb330  /system/lib64/libandroid_runtime.so (android::AndroidRuntime::start+896) (BuildId: 140296c7da6bfd8e3c406c87d6aeb4e4)
W/tter_playground(13128):   native: #167 pc 00002fa6  /system/bin/app_process64 (main+1622) (BuildId: f11cda2b6bb6bff1e502077a2f3e6cf7)
W/tter_playground(13128):   native: #168 pc 00052a3f  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+95) (BuildId: fdd97bce94d2a5e6408dc0732250fe4c)
W/tter_playground(13128):   at java.lang.Throwable.nativeFillInStackTrace(Native method)
W/tter_playground(13128):   at java.lang.Throwable.fillInStackTrace(Throwable.java:819)
W/tter_playground(13128):   at java.lang.Throwable.<init>(Throwable.java:286)
W/tter_playground(13128):   at java.lang.Error.<init>(Error.java:71)
W/tter_playground(13128):   at java.lang.VirtualMachineError.<init>(VirtualMachineError.java:54)
W/tter_playground(13128):   at java.lang.OutOfMemoryError.<init>(OutOfMemoryError.java:58)
W/tter_playground(13128):   at java.lang.Throwable.nativeGetStackTrace(Native method)
W/tter_playground(13128):   at java.lang.Throwable.getOurStackTrace(Throwable.java:869)
W/tter_playground(13128):   at java.lang.Throwable.printStackTrace(Throwable.java:686)
W/tter_playground(13128):   at java.lang.Throwable.printStackTrace(Throwable.java:753)
W/tter_playground(13128):   at android.util.Log.getStackTraceString(Log.java:388)
W/tter_playground(13128):   at io.flutter.plugins.videoplayer.Messages.wrapError(Messages.java:53)
W/tter_playground(13128):   at io.flutter.plugins.videoplayer.Messages$AndroidVideoPlayerApi.lambda$setup$8(Messages.java:890)
W/tter_playground(13128):   at io.flutter.plugins.videoplayer.Messages$AndroidVideoPlayerApi$$ExternalSyntheticLambda10.onMessage(unavailable:2)
W/tter_playground(13128):   at io.flutter.plugin.common.BasicMessageChannel$IncomingMessageHandler.onMessage(BasicMessageChannel.java:219)
W/tter_playground(13128):   at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
W/tter_playground(13128):   at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:322)
W/tter_playground(13128):   at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(unavailable:12)
W/tter_playground(13128):   at android.os.Handler.handleCallback(Handler.java:958)
W/tter_playground(13128):   at android.os.Handler.dispatchMessage(Handler.java:99)
W/tter_playground(13128):   at android.os.Looper.loopOnce(Looper.java:205)
W/tter_playground(13128):   at android.os.Looper.loop(Looper.java:294)
W/tter_playground(13128):   at android.app.ActivityThread.main(ActivityThread.java:8176)
W/tter_playground(13128):   at java.lang.reflect.Method.invoke(Native method)
W/tter_playground(13128):   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
W/tter_playground(13128):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
I/tter_playground(13128): Waiting for a blocking GC Alloc
I/tter_playground(13128): Alloc young concurrent copying GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 191MB/192MB, paused 1.478ms,13us total 1.490s
I/tter_playground(13128): WaitForGcToComplete blocked Background on Alloc for 1.551s
I/tter_playground(13128): Forcing collection of SoftReferences for 1408B allocation
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Waiting for a blocking GC Alloc
I/tter_playground(13128): Waiting for a blocking GC Alloc
I/tter_playground(13128): Clamp target GC heap from 215MB to 192MB
I/tter_playground(13128): Alloc concurrent copying GC freed 114(7144B) AllocSpace objects, 0(0B) LOS objects, 0% free, 191MB/192MB, paused 71us,16us total 77.611ms
I/tter_playground(13128): WaitForGcToComplete blocked Alloc on Alloc for 1.619s
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Forcing collection of SoftReferences for 32B allocation
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Waiting for a blocking GC Alloc
W/tter_playground(13128): Throwing OutOfMemoryError "Failed to allocate a 1408 byte allocation with 3696 free bytes and 3696B until OOM, target footprint 201326592, growth limit 201326592; giving up on allocation because <1% of heap free after GC." (VmSize 19629484 kB)
I/Process (13128): Sending signal. PID: 13128 SIG: 9
Lost connection to device.
Exited

Here are the emulator memory settings:

enter image description here

Is anyone able to reproduce this problem? Any solutions?


Solution

  • I ran the same code into my Android emulator & it is working as expected. I just wanted to say that you do not have to rely on an emulator/simulator. They are not an actual devices. Try it on a physical device because the result may differ or vary on emulators/simulators & real devices.

    Still, face the same issue? Here's another resolution:

    Open AndroidManifest.xml and set property

    android:largeHeap="true"
    

    in the <application> tag.

    So, let me explain the large heap concept:

    Whether the application's processes are created with a large Dalvik heap. This applies to all processes created for the application. It only applies to the first application loaded into a process. If you're using a shared user ID to let multiple applications use a process, they all must use this option consistently to avoid unpredictable results. Most apps don't need this and instead focus on reducing their overall memory usage for improved performance. Enabling this also doesn't guarantee a fixed increase in available memory, because some devices are constrained by their total available memory.

    Reference link: https://developer.android.com/guide/topics/manifest/application-element