781flyingdutchman / background_downloader

Flutter plugin for file downloads and uploads
Other
159 stars 74 forks source link

program stall when downloading 143 files in low speed network #191

Closed erickcchoi closed 11 months ago

erickcchoi commented 11 months ago

Describe the bug Using "FileDownloader().enqueue()" to download 143 files. In low speed network will cause the program stall, even can't click the "+" button to increase the on screen counter. but the program work normally in full speed network.

To Reproduce Steps to reproduce the behavior:

  1. Download the demo code from github : https://github.com/erickcchoi/bug_report
  2. Disable WIFI stopwifi
  3. Change cellular speed to 'EDGE' adjust speed
  4. Click "Start" button to start the test main screen
  5. Keep click "+" to verify the program running status
  6. Change the cellular speed to 'full'.
  7. Click "Start" button to start the test again
  8. Keep click "+" to see the different.

Expected behavior Program running normally in any network speed.

Code

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:background_downloader/background_downloader.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
         colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  int segmentsCounter = 0;
  int totalSegments = 143;
  TaskStatus? downloadTaskStatus;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

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

    FileDownloader().configure(globalConfig: [
      (Config.requestTimeout, const Duration(seconds: 100)),
    ], androidConfig: [
      (Config.useCacheDir, Config.whenAble),
    ], iOSConfig: [
      (Config.localize, {'Cancel': 'StopIt'}),
    ]).then((result) => debugPrint('Configuration result = $result'));

    FileDownloader().updates.listen((update) {
      if (update is TaskStatusUpdate) {
        if (update.status == TaskStatus.complete){
          segmentsCounter++;
          if (segmentsCounter >= totalSegments){
            debugPrint('*** Download completed');
          }
        }
        setState(() {
          downloadTaskStatus = update.status; // update screen
        });
      } else if (update is TaskProgressUpdate) {
        debugPrint('***** Progress update for ${update.task} with progress ${update.progress}');
      }

    });

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const Divider(
              height: 30,
              thickness: 5,
              color: Colors.grey,
            ),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Text('File downloaded $totalSegments : $segmentsCounter'),
            ),
            const SizedBox(height: 10.0),
            ElevatedButton.icon (
              icon: const Icon(Icons.play_arrow, color: Colors.red),
              label: const Text('Start'),
              style: TextButton.styleFrom(
                foregroundColor: Colors.white,
                backgroundColor: Colors.grey,
                shadowColor: Colors.black,
                elevation: 3.0,
              ),
              onPressed: () {
                addTasks();
                setState(() {
                  segmentsCounter = 1;
                });
              },
            ),
            const SizedBox(height: 10.0),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Future<void> addTasks () async {
    for (int i=1; i<totalSegments; i++) {
      await addTask(groupid: 'groupid', fileName: i.toString());
      await Future.delayed(const Duration(milliseconds: 100));
    }
  }

  Future<void> addTask ({required groupid, required String fileName}) async {
    String _baseUri = 'https://europe.olemovienews.com/hlstimeofffmp4/20190930/quzhlBxH/mp4/quzhlBxH.mp4/';
    String url = '${_baseUri}seg-${fileName}-v1-a1.m4s';
    debugPrint('Download Url : $fileName , $url');
    var tsk = DownloadTask(
      url: url,
      group: groupid,
      filename: fileName,
      updates: Updates.status,
      directory: groupid,
      baseDirectory: BaseDirectory.applicationDocuments,
      retries: 3,
      allowPause: true,
    );
    await FileDownloader().enqueue(tsk); // must provide progress updates!
  }
}

https://github.com/erickcchoi/bug_report

781flyingdutchman commented 11 months ago

Hi, once the task is enqueued it is managed by the operating system (iOS or Android), which will control how many tasks are run in parallel. If you want more control over that experience (e.g. limiting the number of tasks that query the same host) then you should look into using a TaskQueue. For example, using the MemoryTaskQueue you can limit the number of concurrent host connections, and that may solve your problem here. It's not perfect though, as the TaskQueue sits in front of the downloader and delays the actual enqueuing of the task, so if your app moves to a suspended state while the queue is not yet empty, those remaining tasks are not enqueued.

Other than that, there's really nothing I can do here, as this all depends on how Android manages background tasks.

erickcchoi commented 10 months ago

Thank you for your quick replay, The "TaskQueue" solution work for me. Thank you.