@@ -47,7 +47,13 @@ class PlayerModel extends ChangeNotifier {
4747
4848 PlayerModel (this .app, this .playlist) {
4949 player = Player ();
50- controller = VideoController (player);
50+ controller = VideoController (
51+ player,
52+ // configuration: VideoControllerConfiguration(
53+ // androidAttachSurfaceAfterVideoParameters: false,
54+ // enableHardwareAcceleration: false,
55+ // ),
56+ );
5157
5258 player.stream.completed.listen ((completed) {
5359 if (completed) {
@@ -194,6 +200,18 @@ class PlayerModel extends ChangeNotifier {
194200 LocalClosedCaptionFile ? get currentCaptions => _currentCaptions;
195201
196202 Future <double > getVideoDuration (String url) async {
203+ // mediakit will parse segment durations for a minute while loading them,
204+ // so we need to parse times itself (or make better exoplayer fork)
205+ if (url.contains ('.m3u8' )) {
206+ try {
207+ final duration = await _getHlsDuration (url);
208+ print ('[getVideoDuration] HLS parsed duration=$duration ' );
209+ return duration;
210+ } catch (e) {
211+ print ('[getVideoDuration] HLS parse failed: $e ' );
212+ return 0 ;
213+ }
214+ }
197215 if (url.contains ('youtu' )) {
198216 url = (await getYoutubeVideoUrl (url)).video;
199217 }
@@ -212,6 +230,63 @@ class PlayerModel extends ChangeNotifier {
212230 return duration.inMilliseconds / 1000 ;
213231 }
214232
233+ Future <double > _getHlsDuration (String url) async {
234+ final response = await http
235+ .get (Uri .parse (url))
236+ .timeout (const Duration (seconds: 10 ));
237+
238+ if (response.statusCode != 200 ) {
239+ // print('[_getHlsDuration] bad status: ${response.statusCode}');
240+ return 0 ;
241+ }
242+
243+ final body = response.body;
244+ // print('[_getHlsDuration] manifest length=${body.length}');
245+
246+ // Master playlist — find and follow the first variant stream
247+ if (body.contains ('#EXT-X-STREAM-INF' )) {
248+ // print('[_getHlsDuration] master playlist detected, following first variant');
249+ final lines = body
250+ .split ('\n ' )
251+ .map ((l) => l.trim ())
252+ .where ((l) => l.isNotEmpty)
253+ .toList ();
254+ for (var i = 0 ; i < lines.length; i++ ) {
255+ if (lines[i].startsWith ('#EXT-X-STREAM-INF' ) && i + 1 < lines.length) {
256+ var variantUrl = lines[i + 1 ];
257+ if (! variantUrl.startsWith ('http' )) {
258+ final base = Uri .parse (url);
259+ variantUrl = base .resolve (variantUrl).toString ();
260+ }
261+ // print('[_getHlsDuration] variant url=$variantUrl');
262+ return _getHlsDuration (variantUrl);
263+ }
264+ }
265+ // print('[_getHlsDuration] no variant found in master playlist');
266+ return 0 ;
267+ }
268+
269+ // Media playlist — sum #EXTINF durations
270+ // Also check for #EXT-X-ENDLIST (VOD vs live)
271+ final isVod = body.contains ('#EXT-X-ENDLIST' );
272+
273+ if (! isVod) {
274+ // Live stream — return sentinel value
275+ // print('[_getHlsDuration] live stream, returning sentinel 356400');
276+ return 356400.0 ;
277+ }
278+
279+ double total = 0 ;
280+ final extinfReg = RegExp (r'#EXTINF:([\d.]+)' );
281+ for (final match in extinfReg.allMatches (body)) {
282+ final seg = double .tryParse (match.group (1 )! ) ?? 0 ;
283+ total += seg;
284+ }
285+
286+ // print('[_getHlsDuration] summed ${extinfReg.allMatches(body).length} segments, total=$total');
287+ return total;
288+ }
289+
215290 static String extractVideoId (String url) {
216291 if (url.contains ('youtu.be/' )) {
217292 return RegExp (r'youtu.be\/([A-z0-9_-]+)' ).firstMatch (url)! .group (1 )! ;
0 commit comments