Support headers

This commit is contained in:
Ryan Heise 2020-06-08 00:20:52 +10:00
parent f66413c007
commit 3dd22f58be
1 changed files with 82 additions and 4 deletions

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@ -72,6 +73,8 @@ class AudioPlayer {
final Future<MethodChannel> _channel; final Future<MethodChannel> _channel;
_ProxyHttpServer _proxy;
final int _id; final int _id;
Future<Duration> _durationFuture; Future<Duration> _durationFuture;
@ -249,14 +252,25 @@ class AudioPlayer {
/// audio, or null if this call was interrupted by another call so [setUrl], /// audio, or null if this call was interrupted by another call so [setUrl],
/// [setFilePath] or [setAsset]. /// [setFilePath] or [setAsset].
/// ///
/// On platforms other than the web, the supplied [headers] will be passed
/// with the request. Currently headers are not recursively applied to items
/// within playlist files such as m3u8.
///
/// On Android, DASH and HLS streams are detected only when the URL's path /// On Android, DASH and HLS streams are detected only when the URL's path
/// has an "mpd" or "m3u8" extension. If the URL does not have such an /// has an "mpd" or "m3u8" extension. If the URL does not have such an
/// extension and you have no control over the server, and you also know the /// extension and you have no control over the server, and you also know the
/// type of the stream in advance, you may as a workaround supply the /// type of the stream in advance, you may as a workaround supply the
/// extension as a URL fragment. e.g. /// extension as a URL fragment. e.g.
/// https://somewhere.com/somestream?x=etc#.m3u8 /// https://somewhere.com/somestream?x=etc#.m3u8
Future<Duration> setUrl(final String url) async { Future<Duration> setUrl(String url, {Map<String, String> headers}) async {
try { try {
if (!kIsWeb && headers != null) {
if (_proxy == null) {
_proxy = _ProxyHttpServer();
await _proxy.start();
}
url = _proxy.addUrl(url, headers);
}
_durationFuture = _invokeMethod('setUrl', [url]).then((ms) => _durationFuture = _invokeMethod('setUrl', [url]).then((ms) =>
(ms == null || ms < 0) (ms == null || ms < 0)
? const Duration(milliseconds: -1) ? const Duration(milliseconds: -1)
@ -437,10 +451,11 @@ class AudioPlayer {
/// * [AudioPlaybackState.none] /// * [AudioPlaybackState.none]
/// * [AudioPlaybackState.connecting] /// * [AudioPlaybackState.connecting]
Future<void> dispose() async { Future<void> dispose() async {
if (this._cacheFile?.existsSync() ?? false) {
this._cacheFile?.deleteSync();
}
await _invokeMethod('dispose'); await _invokeMethod('dispose');
if (_cacheFile?.existsSync() == true) {
_cacheFile?.deleteSync();
}
_proxy?.stop();
await _durationSubject.close(); await _durationSubject.close();
await _eventChannelStreamSubscription.cancel(); await _eventChannelStreamSubscription.cancel();
await _playbackEventSubject.close(); await _playbackEventSubject.close();
@ -593,3 +608,66 @@ enum IosCategory {
playAndRecord, playAndRecord,
multiRoute, multiRoute,
} }
/// A local proxy HTTP server for making remote GET requests with headers.
///
/// TODO: Recursively attach headers to items in playlists like m3u8.
class _ProxyHttpServer {
HttpServer _server;
/// Maps request keys to [_ProxyRequest]s.
final Map<String, _ProxyRequest> _uriMap = {};
/// The port this server is bound to on localhost. This is set only after
/// [start] has completed.
int get port => _server.port;
/// Associate headers with a URL. This may be called only after [start] has
/// completed.
String addUrl(String url, Map<String, String> headers) {
final uri = Uri.parse(url);
final path = _requestKey(uri);
_uriMap[path] = _ProxyRequest(uri, headers);
return uri
.replace(
scheme: 'http',
host: InternetAddress.loopbackIPv4.address,
port: port,
)
.toString();
}
/// A unique key for each request that can be processed by this proxy,
/// made up of the URL path and query string. It is not possible to
/// simultaneously track requests that have the same URL path and query
/// but differ in other respects such as the port or headers.
String _requestKey(Uri uri) => '${uri.path}?${uri.query}';
/// Starts the server.
Future start() async {
_server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
_server.listen((request) async {
if (request.method == 'GET') {
final path = _requestKey(request.uri);
final proxyRequest = _uriMap[path];
final originRequest = await HttpClient().getUrl(proxyRequest.uri);
for (var name in proxyRequest.headers.keys) {
originRequest.headers.add(name, proxyRequest.headers[name]);
}
final originResponse = await originRequest.close();
await originResponse.pipe(request.response);
}
});
}
/// Stops the server
Future stop() => _server.close();
}
/// A request for a URL and headers made by a [_ProxyHttpServer].
class _ProxyRequest {
final Uri uri;
final Map<String, String> headers;
_ProxyRequest(this.uri, this.headers);
}