232 lines
7.7 KiB
Dart
232 lines
7.7 KiB
Dart
// ignore_for_file: avoid_print
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:aves/services/cast/cast_fmp4_fragment_parser.dart';
|
|
import 'package:aves/services/cast/cast_fmp4_progressive_reader.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:integration_test/integration_test.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
void main() {
|
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
const channel = MethodChannel(
|
|
'aves/cast_video_optimizer',
|
|
);
|
|
|
|
testWidgets(
|
|
'genera cinque segmenti TS e la playlist HLS',
|
|
(tester) async {
|
|
final cacheDirectory = await getApplicationCacheDirectory();
|
|
final inputFile = File('${cacheDirectory.path}/stream_test.fmp4');
|
|
final outputDirectory = Directory(
|
|
'${cacheDirectory.path}/fmp4_five_ts_test',
|
|
);
|
|
|
|
expect(
|
|
await inputFile.exists(),
|
|
isTrue,
|
|
reason: 'Copia stream_test.fmp4 nella cache Android prima del test',
|
|
);
|
|
|
|
if (await outputDirectory.exists()) {
|
|
await outputDirectory.delete(recursive: true);
|
|
}
|
|
await outputDirectory.create(recursive: true);
|
|
|
|
const fragmentLimit = 5;
|
|
final fragmentCompleter = Completer<List<Fmp4FragmentInfo>>();
|
|
final fragments = <Fmp4FragmentInfo>[];
|
|
late final CastFmp4ProgressiveReader reader;
|
|
|
|
reader = CastFmp4ProgressiveReader(
|
|
sourcePath: inputFile.path,
|
|
pollingInterval: const Duration(milliseconds: 20),
|
|
onFragment: (fragment) async {
|
|
fragments.add(fragment);
|
|
print(
|
|
'FMP4 FRAGMENT READY '
|
|
'index=${fragments.length - 1} '
|
|
'moofOffset=${fragment.moofOffset} '
|
|
'mdatOffset=${fragment.mdatOffset}',
|
|
);
|
|
|
|
if (fragments.length == fragmentLimit && !fragmentCompleter.isCompleted) {
|
|
fragmentCompleter.complete(List.unmodifiable(fragments));
|
|
reader.requestStop();
|
|
}
|
|
},
|
|
);
|
|
|
|
final readerFuture = reader.start();
|
|
late final List<Fmp4FragmentInfo> parsedFragments;
|
|
|
|
try {
|
|
parsedFragments = await fragmentCompleter.future.timeout(
|
|
const Duration(seconds: 30),
|
|
);
|
|
} finally {
|
|
reader.requestStop();
|
|
await readerFuture;
|
|
}
|
|
|
|
final allAudioSamples = <Fmp4SampleInfo>[];
|
|
final allVideoSamples = <Fmp4SampleInfo>[];
|
|
|
|
for (final fragment in parsedFragments) {
|
|
allAudioSamples.addAll(
|
|
fragment.tracks.singleWhere((track) => track.trackId == 1).samples,
|
|
);
|
|
allVideoSamples.addAll(
|
|
fragment.tracks.singleWhere((track) => track.trackId == 2).samples,
|
|
);
|
|
}
|
|
|
|
expect(allVideoSamples.length, 450);
|
|
expect(allAudioSamples.length, greaterThanOrEqualTo(704));
|
|
|
|
var videoContinuityCounter = 0;
|
|
var audioContinuityCounter = 0;
|
|
var patContinuityCounter = 0;
|
|
var pmtContinuityCounter = 0;
|
|
final reports = <Map<String, dynamic>>[];
|
|
|
|
for (var segmentIndex = 0; segmentIndex < 5; segmentIndex++) {
|
|
final videoStartDts = segmentIndex * 270000;
|
|
final videoEndDts = (segmentIndex + 1) * 270000;
|
|
final audioStartDts = segmentIndex * 144000;
|
|
final audioEndDts = (segmentIndex + 1) * 144000;
|
|
|
|
final selectedVideo = allVideoSamples
|
|
.where(
|
|
(sample) => sample.decodeTime >= videoStartDts && sample.decodeTime < videoEndDts,
|
|
)
|
|
.toList(growable: false);
|
|
|
|
final selectedAudio = allAudioSamples
|
|
.where(
|
|
(sample) => sample.decodeTime >= audioStartDts && sample.decodeTime < audioEndDts,
|
|
)
|
|
.toList(growable: false);
|
|
|
|
expect(
|
|
selectedVideo.length,
|
|
90,
|
|
reason: 'Segmento video $segmentIndex',
|
|
);
|
|
expect(selectedVideo.first.isSync, isTrue);
|
|
expect(selectedAudio.length, inInclusiveRange(140, 141));
|
|
|
|
final outputFile = File(
|
|
'${outputDirectory.path}/'
|
|
'segment_${segmentIndex.toString().padLeft(5, '0')}.ts',
|
|
);
|
|
|
|
final report = await channel.invokeMapMethod<String, dynamic>(
|
|
'writeFmp4FirstTsSegmentTest',
|
|
<String, dynamic>{
|
|
'inputPath': inputFile.path,
|
|
'outputPath': outputFile.path,
|
|
'segmentIndex': segmentIndex,
|
|
'videoStartDts': videoStartDts,
|
|
'videoEndDts': videoEndDts,
|
|
'audioStartDts': audioStartDts,
|
|
'audioEndDts': audioEndDts,
|
|
'initialVideoContinuityCounter': videoContinuityCounter,
|
|
'initialAudioContinuityCounter': audioContinuityCounter,
|
|
'patContinuityCounter': patContinuityCounter,
|
|
'pmtContinuityCounter': pmtContinuityCounter,
|
|
'audioSamples': selectedAudio.map(_sampleToMap).toList(),
|
|
'videoSamples': selectedVideo.map(_sampleToMap).toList(),
|
|
},
|
|
);
|
|
|
|
expect(report, isNotNull);
|
|
expect(report!['success'], isTrue);
|
|
expect(report['segmentIndex'], segmentIndex);
|
|
expect(report['videoSamples'], 90);
|
|
expect(report['audioSamples'], inInclusiveRange(140, 141));
|
|
expect(report['accessUnitDelimiterCount'], 90);
|
|
expect(await outputFile.exists(), isTrue);
|
|
expect(await outputFile.length(), greaterThan(0));
|
|
expect((await outputFile.length()) % 188, 0);
|
|
|
|
videoContinuityCounter = (report['nextVideoContinuityCounter'] as num).toInt();
|
|
audioContinuityCounter = (report['nextAudioContinuityCounter'] as num).toInt();
|
|
patContinuityCounter = (patContinuityCounter + 1) & 0x0f;
|
|
pmtContinuityCounter = (pmtContinuityCounter + 1) & 0x0f;
|
|
reports.add(Map<String, dynamic>.from(report));
|
|
|
|
print(
|
|
'TS SEGMENT PUBLISHED '
|
|
'index=$segmentIndex '
|
|
'videoSamples=${report['videoSamples']} '
|
|
'audioSamples=${report['audioSamples']} '
|
|
'bytes=${report['outputBytes']} '
|
|
'nextVideoCc=$videoContinuityCounter '
|
|
'nextAudioCc=$audioContinuityCounter',
|
|
);
|
|
|
|
if (segmentIndex == 2) {
|
|
print(
|
|
'TS HLS READY '
|
|
'publishedSegments=3 '
|
|
'contentDurationSeconds=9',
|
|
);
|
|
}
|
|
}
|
|
|
|
final playlist = File('${outputDirectory.path}/master.m3u8');
|
|
await playlist.writeAsString(
|
|
_buildVodPlaylist(segmentCount: reports.length),
|
|
flush: true,
|
|
);
|
|
|
|
expect(await playlist.exists(), isTrue);
|
|
expect(await playlist.length(), greaterThan(0));
|
|
|
|
print(
|
|
'FMP4 FIVE TS TEST PASS '
|
|
'segments=${reports.length} '
|
|
'playlist=${playlist.path} '
|
|
'outputDirectory=${outputDirectory.path}',
|
|
);
|
|
|
|
print('FILES READY FOR ADB COPY waitingSeconds=30');
|
|
await Future<void>.delayed(const Duration(seconds: 30));
|
|
},
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> _sampleToMap(Fmp4SampleInfo sample) {
|
|
return <String, dynamic>{
|
|
'offset': sample.absoluteOffset,
|
|
'size': sample.size,
|
|
'dts': sample.decodeTime,
|
|
'pts': sample.presentationTime,
|
|
'duration': sample.duration,
|
|
'sync': sample.isSync,
|
|
};
|
|
}
|
|
|
|
String _buildVodPlaylist({required int segmentCount}) {
|
|
final buffer = StringBuffer()
|
|
..writeln('#EXTM3U')
|
|
..writeln('#EXT-X-VERSION:3')
|
|
..writeln('#EXT-X-TARGETDURATION:3')
|
|
..writeln('#EXT-X-MEDIA-SEQUENCE:0')
|
|
..writeln('#EXT-X-PLAYLIST-TYPE:VOD')
|
|
..writeln('#EXT-X-INDEPENDENT-SEGMENTS');
|
|
|
|
for (var index = 0; index < segmentCount; index++) {
|
|
buffer
|
|
..writeln('#EXTINF:3.000,')
|
|
..writeln('segment_${index.toString().padLeft(5, '0')}.ts');
|
|
}
|
|
|
|
buffer.writeln('#EXT-X-ENDLIST');
|
|
return buffer.toString();
|
|
}
|