florent37 / Flutter-AssetsAudioPlayer

Play simultaneously music/audio from assets/network/file directly from Flutter, compatible with android / ios / web / macos, displays notifications
https://pub.dartlang.org/packages/assets_audio_player
Apache License 2.0
752 stars 357 forks source link

Some HLS streams with relative index paths fail (unsupported URL, error code -1002) #756

Open mt633 opened 1 year ago

mt633 commented 1 year ago

Flutter Version

My version : 3.3.1

Lib Version

My version : 3.0.6

Platform (Android / iOS / web) + version

Platform : iOS 16.2

Describe the bug

We provide HLS content (not live) and while testing the library I discovered that some files sometimes fail.

The error code is -1002 which means it is an unsupported URL. The URL which it fails with is in itself OK (it looks something like this: https://mydomain.com/stream/audio4/master.m3u8) and works if I test it manually.

The file contents look like this:

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=105600,CODECS="mp4a.40.2"
index_0.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=70400,CODECS="mp4a.40.2"
index_1.m3u8

In other words, there are two different streams based on bandwidth with two different index files stored in the same directory.

When I enabled CFNETWORK_DIAGNOSTICS I discovered that it somewhere fails to add the full path to the index file

Task <7D7F4301-5BA5-4850-AF4C-04224BEED6E5>.<367> finished with error [-1002] Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={NSLocalizedDescription=unsupported URL, NSErrorFailingURLStringKey=index_0.m3u8, NSErrorFailingURLKey=index_0.m3u8, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <7D7F4301-5BA5-4850-AF4C-04224BEED6E5>.<367>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <7D7F4301-5BA5-4850-AF4C-04224BEED6E5>.<367>, NSUnderlyingError=0x600001e0a310 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}}

In other words, it tries to open index_0.m3u8 as the URL instead of https://[full-path-to-server-from-previous-file]/index_0.m3u8. Given that it works 4 out of 5 times with this library, this is a very strange behavior.

My question is if the HLS is processed by this library and therefore is a bug that should be solved here or is that handled by native Apple code? I have searched the web but so far no success in finding similar issues.

Small code to reproduce

import 'package:assets_audio_player/assets_audio_player.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State {

  final AssetsAudioPlayer _assetsAudioPlayer = AssetsAudioPlayer();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: RaisedButton(
            child: Text("open"),
            onPressed: () {
              await _assetsAudioPlayer.open(
                Audio.network(
                  // I cannot share a link to our system, this is just another HLS I found somewhere
                  'https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8',
                ),
              );
            }
          ),
        ),
      ),
    );
  }
}
mt633 commented 1 year ago

As a workaround, I've added some code here to pass the error through to the Flutter error handler instead which allows me to retry the playback and it seems like it always works the second time:

 case .failed:
      debugPrint("playback failed")
      if (item.error is URLError && (item.error as! URLError).code == URLError.unsupportedURL) {
          self?.onError(AssetAudioPlayerError(type: "url", message: "avplayer error"))
          return
      }

And on the Flutter side, something like this.

player.onErrorDo = (handler) {
      if (errorCount < 3) {
        handler.player.open(
            handler.playlist!.copyWith(startIndex: handler.playlistIndex),
            seek: handler.currentPosition);
        errorCount++;
      } else {
       // TODO: Handle errors not solved by retrying
      }
    };