Aves_mio0.88/integration_test/fmp4_five_ts_segments_test.dart
2026-10-03 12:51:15 +02:00

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();
}