Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/mpv/player/platform/player_tizen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ class PlayerTizen extends PlayerBase with VideoRectSupport {
'videoWidth': _width,
'videoHeight': _height,
'playerType': 'tizen',
'videoDecoder': 'unknown',
};
@override
Future<String?> getProperty(String name) async => switch (name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class _PlayerPerformanceOverlayState extends State<PlayerPerformanceOverlay> {
@override
Widget build(BuildContext context) {
final isMpv = _stats.playerType == 'mpv';
// `!isMpv` used to mean ExoPlayer, back when those were the only two
// backends. Tizen is a third, and these metrics are ExoPlayer's alone, so
// ask for it by name rather than by elimination.
final isExo = _stats.playerType == 'exoplayer';

final sections = <Widget>[
_buildSection(Symbols.videocam_rounded, t.fileInfo.video, [
Expand All @@ -76,8 +80,8 @@ class _PlayerPerformanceOverlayState extends State<PlayerPerformanceOverlay> {
if (_stats.hasValidVideoFps) _metric(t.performanceOverlay.fps, _stats.videoFpsFormatted),
if (_stats.hasValidVideoBitrate) _metric(t.fileInfo.bitrate, _stats.videoBitrateFormatted),
_metric(t.performanceOverlay.decoder, _stats.hwdecFormatted),
if (!isMpv && _stats.videoDecoderName != null) _metric(t.performanceOverlay.rawDecoder, _stats.videoDecoderRaw),
if (!isMpv) _metric(t.performanceOverlay.tunneling, _stats.tunneledPlaybackFormatted),
if (isExo && _stats.videoDecoderName != null) _metric(t.performanceOverlay.rawDecoder, _stats.videoDecoderRaw),
if (isExo) _metric(t.performanceOverlay.tunneling, _stats.tunneledPlaybackFormatted),
if (_stats.aspectName != null && _stats.aspectName!.isNotEmpty)
_metric(t.performanceOverlay.aspect, _stats.aspectName!),
if (_stats.rotate != null && _stats.rotate != 0) _metric(t.performanceOverlay.rotation, _stats.rotateFormatted),
Expand All @@ -91,7 +95,7 @@ class _PlayerPerformanceOverlayState extends State<PlayerPerformanceOverlay> {
_metric(t.performanceOverlay.sampleRate, _stats.sampleRateFormatted),
_metric(t.fileInfo.channels, _stats.audioChannels ?? t.common.notAvailable),
if (_stats.hasValidAudioBitrate) _metric(t.fileInfo.bitrate, _stats.audioBitrateFormatted),
if (!isMpv && _stats.audioDecoderName != null)
if (isExo && _stats.audioDecoderName != null)
_metric(t.performanceOverlay.decoder, _stats.audioDecoderFormatted),
]),
if (isMpv)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,13 @@ class PerformanceStats {
}
return t.performanceOverlay.decoderSoftware;
}
// For MPV, use hwdec-current property
if (hwdecCurrent == null || hwdecCurrent!.isEmpty || hwdecCurrent == 'no') {
return t.performanceOverlay.decoderSoftware;
}
// For MPV, use hwdec-current property. Only mpv's own `no` is evidence of
// software decoding; an absent or empty value means the backend never told
// us, which is not the same claim. Backends that decode outside the app
// (Tizen's Capi plane) never report one, and asserting "Software" there is
// the exact error ACCEPTANCE.md forbids in the other direction.
if (hwdecCurrent == 'no') return t.performanceOverlay.decoderSoftware;
if (hwdecCurrent == null || hwdecCurrent!.isEmpty) return t.common.notAvailable;
return hwdecCurrent!;
}

Expand Down Expand Up @@ -372,6 +375,7 @@ class PerformanceStats {
return switch (playerType.toLowerCase()) {
'mpv' => 'MPV',
'exoplayer' => 'ExoPlayer',
'tizen' => 'Tizen',
_ => playerType,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@ class PerformanceStatsService {
uiFps: _currentUiFps,
);
_emit(stats);
} else if (playerType == 'tizen') {
// The Capi backend decodes on the TV's own SoC, outside Flutter, and
// exposes no decoder name, hwdec state or frame counters. Report only
// what it actually returns; every other metric stays null so the
// overlay prints N/A rather than inventing a value. Without this branch
// Tizen fell through to the ExoPlayer parse below, which labelled the
// backend "ExoPlayer" and left hwdec null - read as "Software".
final stats = PerformanceStats(
playerType: 'tizen',
videoWidth: statsMap['videoWidth'] as int?,
videoHeight: statsMap['videoHeight'] as int?,
appMemoryBytes: appMemory,
uiFps: _currentUiFps,
);
_emit(stats);
} else {
// Parse ExoPlayer stats format
final stats = PerformanceStats(
Expand Down
28 changes: 28 additions & 0 deletions test/widgets/performance_stats_service_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/i18n/strings.g.dart';
import 'package:plezy/mpv/mpv.dart';
import 'package:plezy/widgets/video_controls/widgets/performance_overlay/performance_stats.dart';
import 'package:plezy/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart';
Expand Down Expand Up @@ -292,4 +293,31 @@ void main() {
expect(stats.droppedFramesFormatted, '3');
});
});

group('a backend that decodes outside the app', () {
test('Tizen reports itself, not ExoPlayer', () async {
final stats = await _firstStats(
_NativeStatsPlayer({'playerType': 'tizen', 'videoWidth': 1920, 'videoHeight': 1080}),
);

expect(stats.playerType, 'tizen');
expect(stats.playerTypeFormatted, 'Tizen');
expect(stats.videoWidth, 1920);
// Falling through to the ExoPlayer parse used to invent these.
expect(stats.videoDecoderName, isNull);
});

test('no hwdec signal reads as unavailable, never as software', () {
// The Capi plane exposes no hwdec state. Claiming software decoding
// there asserts a measurement nobody took.
const stats = PerformanceStats(playerType: 'tizen');
expect(stats.hwdecFormatted, isNot(t.performanceOverlay.decoderSoftware));
expect(stats.hwdecFormatted, t.common.notAvailable);
});

test("mpv's own 'no' is still real evidence of software decoding", () {
const stats = PerformanceStats(playerType: 'mpv', hwdecCurrent: 'no');
expect(stats.hwdecFormatted, t.performanceOverlay.decoderSoftware);
});
});
}