Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PE-4732: video-preview-doesnt-accept-all-mov-and-mpg-formats #1405

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 1 addition & 2 deletions lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,7 @@ class FsEntryPreviewCubit extends Cubit<FsEntryPreviewState> {
return audioContentTypes
.any((element) => element.contains(fileExtension));
case 'video':
return videoContentTypes
.any((element) => element.contains(fileExtension));
return true;
default:
return false;
}
Expand Down
271 changes: 156 additions & 115 deletions lib/pages/drive_detail/components/fs_entry_preview_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,31 @@ class VideoPlayerWidget extends StatefulWidget {
class _VideoPlayerWidgetState extends State<VideoPlayerWidget>
with AutomaticKeepAliveClientMixin {
late VideoPlayerController _videoPlayerController;
late VideoPlayer _videoPlayer;
bool _isVolumeSliderVisible = false;
bool _wasPlaying = false;
final _menuController = MenuController();
final Lock _lock = Lock();
String? _errorMessage;

@override
void initState() {
logger.d('Initializing video player: ${widget.videoUrl}');
super.initState();
_videoPlayerController =
VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
_videoPlayerController.initialize();
_videoPlayerController.addListener(_listener);
_videoPlayer =
VideoPlayer(_videoPlayerController, key: const Key('videoPlayer'));
_videoPlayerController.initialize().then((v) {
_videoPlayerController.addListener(_listener);
// force refresh
setState(() {});
}).catchError((err) {
final formatError =
err.toString().contains('MEDIA_ERR_SRC_NOT_SUPPORTED');
setState(() {
_errorMessage = formatError
? 'File type unsupported by browser or operating system'
: appLocalizationsOf(context).couldNotLoadFile;
});
});
}

@override
Expand All @@ -121,18 +130,15 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget>
setState(() {
if (_videoPlayerController.value.hasError) {
logger.e('>>> ${_videoPlayerController.value.errorDescription}');
setState(() {
final formatError = _videoPlayerController.value.errorDescription
?.contains('MEDIA_ERR_SRC_NOT_SUPPORTED') ??
false;

// In case of emergency, reinitialize the video player.
_videoPlayerController.removeListener(_listener);
_videoPlayerController.dispose();

_videoPlayerController =
VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
_videoPlayerController.initialize();
_videoPlayerController.addListener(_listener);

_videoPlayer =
VideoPlayer(_videoPlayerController, key: const Key('videoPlayer'));
_errorMessage = formatError
? 'File type unsupported by browser or operating system'
kunstmusik marked this conversation as resolved.
Show resolved Hide resolved
: appLocalizationsOf(context).couldNotLoadFile;
});
}
});
}
Expand Down Expand Up @@ -178,10 +184,14 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget>
Widget build(BuildContext context) {
super.build(context);

var colors = ArDriveTheme.of(context).themeData.colors;
var videoValue = _videoPlayerController.value;
var currentTime = getTimeString(videoValue.position);
var duration = getTimeString(videoValue.duration);
final colors = ArDriveTheme.of(context).themeData.colors;
final videoValue = _videoPlayerController.value;
final currentTime = getTimeString(videoValue.position);
final duration = getTimeString(videoValue.duration);

final controlsEnabled = videoValue.isInitialized &&
videoValue.duration > Duration.zero &&
_errorMessage == null;

return VisibilityDetector(
key: const Key('video-player'),
Expand All @@ -208,15 +218,27 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget>
children: [
Container(color: Colors.black),
Center(
child: AspectRatio(
aspectRatio: _videoPlayerController.value.aspectRatio,
child: _videoPlayer)),
child: _errorMessage != null
? Padding(
padding: const EdgeInsets.all(20),
child: Text(
_errorMessage ?? '',
textAlign: TextAlign.center,
style: ArDriveTypography.body
.smallBold700(color: colors.themeFgMuted)
.copyWith(fontSize: 13),
))
: AspectRatio(
aspectRatio: _videoPlayerController.value.aspectRatio,
child: VideoPlayer(_videoPlayerController,
key: const Key('videoPlayer')))),
],
))),
Padding(
padding: const EdgeInsets.fromLTRB(24, 8, 24, 32),
child: Column(children: [
Text(widget.filename,
textAlign: TextAlign.center,
style: ArDriveTypography.body
.smallBold700(color: colors.themeFgDefault)),
const SizedBox(height: 8),
Expand All @@ -238,48 +260,54 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget>
videoValue.duration.inMilliseconds.toDouble()),
min: 0.0,
max: videoValue.duration.inMilliseconds.toDouble(),
onChangeStart: (v) async {
if (_videoPlayerController.value.duration >
Duration.zero) {
_wasPlaying =
_videoPlayerController.value.isPlaying;
if (_wasPlaying) {
await _lock.synchronized(() async {
await _videoPlayerController
.pause()
.catchError((e) {
logger.e('Error pausing video: $e');
onChangeStart: !controlsEnabled
? null
: (v) async {
if (_videoPlayerController.value.duration >
Duration.zero) {
_wasPlaying =
_videoPlayerController.value.isPlaying;
if (_wasPlaying) {
await _lock.synchronized(() async {
await _videoPlayerController
.pause()
.catchError((e) {
logger.e('Error pausing video: $e');
});
});
setState(() {});
}
}
},
onChanged: !controlsEnabled
? null
: (v) async {
setState(() {
final milliseconds = v.toInt();

if (_videoPlayerController.value.duration >
Duration.zero) {
_videoPlayerController.seekTo(
Duration(milliseconds: milliseconds));
}
});
});
setState(() {});
}
}
},
onChanged: (v) async {
setState(() {
final milliseconds = v.toInt();

if (_videoPlayerController.value.duration >
Duration.zero) {
_videoPlayerController
.seekTo(Duration(milliseconds: milliseconds));
}
});
},
onChangeEnd: (v) async {
if (_videoPlayerController.value.duration >
Duration.zero &&
_wasPlaying) {
await _lock.synchronized(() async {
await _videoPlayerController
.play()
.catchError((e) {
logger.e('Error playing video: $e');
});
});
setState(() {});
}
})),
},
onChangeEnd: !controlsEnabled
? null
: (v) async {
if (_videoPlayerController.value.duration >
Duration.zero &&
_wasPlaying) {
await _lock.synchronized(() async {
await _videoPlayerController
.play()
.catchError((e) {
logger.e('Error playing video: $e');
});
});
setState(() {});
}
})),
const SizedBox(height: 4),
Row(
children: [
Expand All @@ -304,9 +332,11 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget>
alignment: Alignment.centerLeft,
child: ScreenTypeLayout.builder(
mobile: (context) => IconButton(
onPressed: () {
goFullScreen();
},
onPressed: !controlsEnabled
? null
: () {
goFullScreen();
},
icon: const Icon(Icons.fullscreen_outlined,
size: 24)),
desktop: (context) => VolumeSliderWidget(
Expand All @@ -325,37 +355,41 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget>
),
))),
MaterialButton(
onPressed: () async {
final value = _videoPlayerController.value;
if (!value.isInitialized ||
value.isBuffering ||
value.duration <= Duration.zero) {
return;
}
if (value.isPlaying) {
await _lock.synchronized(() async {
await _videoPlayerController
.pause()
.catchError((e) {
logger.e('Error pausing video: $e');
});
});
} else {
if (value.position >= value.duration) {
_videoPlayerController.seekTo(Duration.zero);
}

await _lock.synchronized(() async {
await _videoPlayerController
.play()
.catchError((e) {
logger.e('Error playing video: $e');
});
});
setState(() {});
}
},
onPressed: !controlsEnabled
? null
: () async {
final value = _videoPlayerController.value;
if (!value.isInitialized ||
value.isBuffering ||
value.duration <= Duration.zero) {
return;
}
if (value.isPlaying) {
await _lock.synchronized(() async {
await _videoPlayerController
.pause()
.catchError((e) {
logger.e('Error pausing video: $e');
});
});
} else {
if (value.position >= value.duration) {
_videoPlayerController
.seekTo(Duration.zero);
}

await _lock.synchronized(() async {
await _videoPlayerController
.play()
.catchError((e) {
logger.e('Error playing video: $e');
});
});
setState(() {});
}
},
color: colors.themeAccentBrand,
disabledColor: colors.themeAccentDisabled,
shape: const CircleBorder(),
child: Padding(
padding: const EdgeInsets.all(8),
Expand Down Expand Up @@ -422,9 +456,11 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget>
size: 24))),
ScreenTypeLayout.builder(
desktop: (context) => IconButton(
onPressed: () {
goFullScreen();
},
onPressed: !controlsEnabled
? null
: () {
goFullScreen();
},
icon: const Icon(Icons.fullscreen_outlined,
size: 24)),
mobile: (context) => const SizedBox.shrink(),
Expand Down Expand Up @@ -477,6 +513,7 @@ class _FullScreenVideoPlayerWidgetState
bool _controlsVisible = true;
Timer? _hideControlsTimer;
final Lock _lock = Lock();
String? _errorMessage;

@override
void initState() {
Expand All @@ -497,8 +534,10 @@ class _FullScreenVideoPlayerWidgetState
logger.e('Error playing video: $e');
});
} else {
_videoPlayer = VideoPlayer(_videoPlayerController,
key: const Key('videoPlayer'));
setState(() {
_videoPlayer = VideoPlayer(_videoPlayerController,
key: const Key('videoPlayer'));
});
}
});
});
Expand All @@ -522,18 +561,9 @@ class _FullScreenVideoPlayerWidgetState
setState(() {
if (_videoPlayerController.value.hasError) {
logger.e('>>> ${_videoPlayerController.value.errorDescription}');

// In case of emergency, reinitialize the video player.
_videoPlayerController.removeListener(_listener);
_videoPlayerController.dispose();

_videoPlayerController =
VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
_videoPlayerController.initialize();
_videoPlayerController.addListener(_listener);

_videoPlayer =
VideoPlayer(_videoPlayerController, key: const Key('videoPlayer'));
setState(() {
_errorMessage = appLocalizationsOf(context).couldNotLoadFile;
});
}
});
}
Expand Down Expand Up @@ -574,7 +604,17 @@ class _FullScreenVideoPlayerWidgetState
Center(
child: AspectRatio(
aspectRatio: _videoPlayerController.value.aspectRatio,
child: _videoPlayer ?? const SizedBox.shrink(),
child: _errorMessage != null
? Padding(
padding: const EdgeInsets.all(20),
child: Text(
_errorMessage ?? '',
textAlign: TextAlign.center,
style: ArDriveTypography.body
.smallBold700(color: colors.themeFgMuted)
.copyWith(fontSize: 13),
))
: _videoPlayer ?? const SizedBox.shrink(),
)),
MouseRegion(
onHover: (event) {
Expand Down Expand Up @@ -1077,6 +1117,7 @@ class _AudioPlayerWidgetState extends State<AudioPlayerWidget>
padding: const EdgeInsets.fromLTRB(24, 20, 24, 32),
child: Column(children: [
Text(widget.filename,
textAlign: TextAlign.center,
style: ArDriveTypography.body
.smallBold700(color: colors.themeFgDefault)),
const SizedBox(height: 8),
Expand Down
Loading