Files
reelbox/test/native_player_test.dart

270 lines
8.4 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:reelbox/media/native_player.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('org.reelbox/media');
setUp(() => debugDefaultTargetPlatformOverride = TargetPlatform.android);
tearDown(() {
debugDefaultTargetPlatformOverride = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, null);
});
test('awaits exit and sends exact URL, title and checkpoint', () async {
final exit = Completer<Map<String, Object>>();
MethodCall? received;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) {
received = call;
return exit.future;
});
var returned = false;
const url = 'https://media.example.org/a%2Fb.m3u8?sig=a%2Bz&x=1';
final pending =
NativePlayerBridge.open(
url: url,
title: 'عنوان — episode',
position: const Duration(milliseconds: 12345),
autoplay: false,
externalPackage: 'vlc',
orientation: 'landscape',
videoFit: 'cover',
speed: 1.25,
subtitleEnabled: false,
).then((result) {
returned = true;
return result;
});
await Future<void>.delayed(Duration.zero);
expect(returned, isFalse);
expect(received!.method, 'openNativePlayer');
expect(received!.arguments, containsPair('url', url));
expect(received!.arguments, containsPair('positionMs', 12345));
expect(received!.arguments, containsPair('autoplay', false));
expect(received!.arguments, containsPair('externalPackage', 'vlc'));
expect(received!.arguments, containsPair('subtitleEnabled', false));
expect(received!.arguments, containsPair('orientation', 'landscape'));
expect(received!.arguments, containsPair('videoFit', 'cover'));
expect(received!.arguments, containsPair('speed', 1.25));
exit.complete({
'positionMs': 42000,
'durationMs': 90000,
'opened': true,
'completed': false,
});
final result = await pending;
expect(result.position.inMilliseconds, 42000);
expect(result.duration.inMilliseconds, 90000);
expect(result.opened, isTrue);
expect(result.completed, isFalse);
});
test(
'local and loopback sources and optional subtitles are unchanged',
() async {
final calls = <MethodCall>[];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
calls.add(call);
return {
'positionMs': 0,
'durationMs': 0,
'opened': false,
'completed': false,
};
});
for (final url in [
'/data/user/0/org.reelbox.app/files/فیلم.mkv',
'http://127.0.0.1:49123/media/lease/index.m3u8?token=a%2Bz',
]) {
final result = await NativePlayerBridge.open(
url: url,
title: 'Film',
subtitleUrl: 'https://media.example.org/sub.vtt?token=a%2Fz',
hardwareAcceleration: false,
pipEnabled: false,
);
expect(calls.last.arguments, containsPair('url', url));
expect(
calls.last.arguments,
containsPair(
'subtitleUrl',
'https://media.example.org/sub.vtt?token=a%2Fz',
),
);
expect(
calls.last.arguments,
containsPair('hardwareAcceleration', false),
);
expect(calls.last.arguments, containsPair('pipEnabled', false));
expect(result.opened, isFalse);
}
expect(calls, hasLength(2));
},
);
test(
'rejects malformed exit maps instead of manufacturing progress',
() async {
for (final response in [
null,
{
'positionMs': -1,
'durationMs': 10,
'opened': true,
'completed': false,
},
{
'positionMs': double.nan,
'durationMs': 10,
'opened': true,
'completed': false,
},
{
'positionMs': 0x7fffffffffffffff,
'durationMs': 0,
'opened': true,
'completed': false,
},
{
'positionMs': 1.5,
'durationMs': 10,
'opened': true,
'completed': false,
},
{
'positionMs': 10,
'durationMs': -1,
'opened': true,
'completed': false,
},
{
'positionMs': 10,
'durationMs': 20,
'opened': 'true',
'completed': true,
},
]) {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (_) async => response);
await expectLater(
NativePlayerBridge.open(
url: 'https://media.example.org/video.mp4',
title: 'Film',
),
throwsFormatException,
);
}
final bounded = NativePlaybackResult.fromMap({
'positionMs': 1000,
'durationMs': 100,
'opened': false,
'completed': true,
});
expect(bounded.position.inMilliseconds, 100);
expect(bounded.completed, isFalse);
},
);
test('completion requires a credible near-end checkpoint, not just EOF', () {
for (final sample in <Map<String, Object>>[
{'positionMs': 180000, 'durationMs': 3600000},
{'positionMs': 180000, 'durationMs': 0},
{'positionMs': 0, 'durationMs': 3600000},
{'positionMs': 3600001, 'durationMs': 3600000},
{'positionMs': 3597000, 'durationMs': 3600000},
{'positionMs': 3600000, 'durationMs': 3600000, 'opened': false},
{'positionMs': 3600000, 'durationMs': 3600000, 'completed': false},
]) {
final result = NativePlaybackResult.fromMap({
'opened': true,
'completed': true,
...sample,
});
expect(result.completed, isFalse, reason: sample.toString());
}
for (final position in [3599500, 3600000]) {
final result = NativePlaybackResult.fromMap({
'positionMs': position,
'durationMs': 3600000,
'opened': true,
'completed': true,
});
expect(result.completed, isTrue);
}
});
test('platform failures and busy results propagate without retry', () async {
var calls = 0;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (_) async {
calls++;
throw PlatformException(code: 'NATIVE_PLAYER_BUSY');
});
await expectLater(
NativePlayerBridge.open(
url: 'https://media.example.org/video.mp4',
title: 'Film',
),
throwsA(isA<PlatformException>()),
);
expect(calls, 1);
});
test('unsupported platforms do not call Android', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.linux;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
channel,
(_) async => fail('Android was invoked'),
);
await expectLater(
NativePlayerBridge.open(
url: 'https://media.example.org/video.mp4',
title: 'Film',
),
throwsUnsupportedError,
);
});
test('invalid launch preferences fail before starting an activity', () async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (_) async {
fail('Invalid preferences reached Android');
});
await expectLater(
NativePlayerBridge.open(
url: 'https://media.example.org/video.mp4',
title: 'Film',
speed: double.nan,
),
throwsArgumentError,
);
await expectLater(
NativePlayerBridge.open(
url: 'https://media.example.org/video.mp4',
title: 'Film',
orientation: 'invalid',
),
throwsArgumentError,
);
await expectLater(
NativePlayerBridge.open(
url: 'https://media.example.org/video.mp4',
title: 'Film',
videoFit: 'invalid',
),
throwsArgumentError,
);
await expectLater(
NativePlayerBridge.open(url: '', title: 'Film'),
throwsArgumentError,
);
});
}