Open mwesigwadi opened 2 months ago
Any help on this ?
@mwesigwadi Hello, The video_player_avplay plugin's VideoPlayer widget is not based on Texture. The plugin updates native player's position and size by lisen for the VideoPlayer's position and size:
WidgetsBinding.instance.addPostFrameCallback(_afterFrameLayout);
void _afterFrameLayout(_) {
if (widget.controller.value.isInitialized) {
final Rect currentRect = _getCurrentRect();
if (currentRect != Rect.zero && _playerRect != currentRect) {
_videoPlayerPlatform.setDisplayGeometry(
_playerId,
currentRect.left.toInt(),
currentRect.top.toInt(),
currentRect.width.toInt(),
currentRect.height.toInt(),
);
_playerRect = currentRect;
}
}
WidgetsBinding.instance.addPostFrameCallback(_afterFrameLayout);
}
Hello @xiaowei-guan thanks for the response, however can't this be dynamic like the official video player ?. It would be great if the videoPlayer could span any given space respecting constraints.
I have tried and the player still gets resized not covering the current screen size.
Checkout my resusable videoplayer widget.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:hotel_iptv/core/constants/application_colors.dart';
import 'package:hotel_iptv/features/videoplayer/application/controllers/video_player_controller.dart';
import 'package:provider/provider.dart';
import 'package:video_player_avplay/video_player.dart';
import 'package:video_player_avplay/video_player_platform_interface.dart';
import '../../../../core/utils/navigation_intents.dart';
class VideoPlayerScreen extends StatefulWidget {
const VideoPlayerScreen({
super.key,
required this.url,
this.looping = false,
this.backgroundImage,
});
final String url;
final bool? looping;
final String? backgroundImage;
@override
State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen>
with SingleTickerProviderStateMixin {
VideoPlayerController? _controller;
@override
void initState() {
super.initState();
if (widget.url.isNotEmpty) {
_initializeController();
}
}
@override
void didUpdateWidget(covariant VideoPlayerScreen oldWidget) {
if (oldWidget.url != widget.url) {
if (_controller != null) {
_disposeController();
if (widget.url.isNotEmpty) {
_initializeController();
}
} else {
if (widget.url.isNotEmpty) {
_initializeController();
}
}
}
super.didUpdateWidget(oldWidget);
}
void _onVideoPLayerStateChanged() {
if (_controller == null) return;
if (_controller!.value.isPlaying) {
context.read<VideoController>().setIsPlaying(true);
} else {
context.read<VideoController>().setIsPlaying(false);
}
}
void _initializeController() {
_controller = VideoPlayerController.network(
playerOptions: {},
widget.url,
formatHint: VideoFormat.dash,
)
..setLooping(widget.looping!)
..initialize().then((_) {
setState(() {
_controller?.play();
});
});
_controller?.addListener(_onVideoPLayerStateChanged);
}
_disposeController() {
_controller?.removeListener(_onVideoPLayerStateChanged);
_controller?.deactivate();
_controller?.dispose();
}
@override
void dispose() {
_disposeController();
super.dispose();
}
@override
Widget build(BuildContext context) {
// print(_controller?.value.size);
return Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.enter): const IntentEnter(),
},
child: Actions(
actions: {
IntentEnter: CallbackAction<IntentEnter>(
onInvoke: (IntentEnter intent) =>
_handleKey(context, LogicalKeyboardKey.enter),
),
},
child: Scaffold(
backgroundColor: ApplicationColors.black,
body: Builder(builder: (context) {
if (widget.url.isNotEmpty && _controller != null) {
return !_controller!.value.isInitialized
? Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Colors.black,
image: widget.backgroundImage == null
? null
: DecorationImage(
image: NetworkImage(widget
.backgroundImage ??
'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0c/15/83/36/zona-de-aguas.jpg?w=1200&h=-1&s=1'),
fit: BoxFit.fill)),
)
: VideoPlayer(_controller!);
} else {
return Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
color: ApplicationColors.black,
);
}
})),
),
);
}
void _handleKey(BuildContext context, LogicalKeyboardKey key) {
if (key == LogicalKeyboardKey.enter) {
context.read<VideoController>().setIsOverlay(true);
}
}
}
`
I have tried and the player still gets resized not covering the current screen size.
Checkout my resusable videoplayer widget.
`import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:hotel_iptv/core/constants/application_colors.dart'; import 'package:hotel_iptv/features/videoplayer/application/controllers/video_player_controller.dart'; import 'package:provider/provider.dart'; import 'package:video_player_avplay/video_player.dart'; import 'package:video_player_avplay/video_player_platform_interface.dart';
import '../../../../core/utils/navigation_intents.dart';
class VideoPlayerScreen extends StatefulWidget { const VideoPlayerScreen({ super.key, required this.url, this.looping = false, this.backgroundImage, }); final String url; final bool? looping; final String? backgroundImage; @OverRide State createState() => _VideoPlayerScreenState(); }
class _VideoPlayerScreenState extends State with SingleTickerProviderStateMixin { VideoPlayerController? _controller;
@OverRide void initState() { super.initState(); if (widget.url.isNotEmpty) { _initializeController(); } }
@OverRide void didUpdateWidget(covariant VideoPlayerScreen oldWidget) { if (oldWidget.url != widget.url) { if (_controller != null) { _disposeController(); if (widget.url.isNotEmpty) { _initializeController(); } } else { if (widget.url.isNotEmpty) { _initializeController(); } } }
super.didUpdateWidget(oldWidget);
}
void _onVideoPLayerStateChanged() { if (_controller == null) return; if (_controller!.value.isPlaying) { context.read().setIsPlaying(true); } else { context.read().setIsPlaying(false); } }
void _initializeController() { controller = VideoPlayerController.network( playerOptions: {}, widget.url, formatHint: VideoFormat.dash, ) ..setLooping(widget.looping!) ..initialize().then(() { setState(() { _controller?.play(); }); }); _controller?.addListener(_onVideoPLayerStateChanged); }
_disposeController() { _controller?.removeListener(_onVideoPLayerStateChanged); _controller?.deactivate(); _controller?.dispose(); }
@OverRide void dispose() { _disposeController(); super.dispose(); }
@OverRide Widget build(BuildContext context) { // print(_controller?.value.size); return Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.enter): const IntentEnter(), }, child: Actions( actions: { IntentEnter: CallbackAction( onInvoke: (IntentEnter intent) => _handleKey(context, LogicalKeyboardKey.enter), ), }, child: Scaffold( backgroundColor: ApplicationColors.black, body: Builder(builder: (context) { if (widget.url.isNotEmpty && _controller != null) { return !_controller!.value.isInitialized ? Container( width: ScreenUtil().screenWidth, height: ScreenUtil().screenHeight, clipBehavior: Clip.hardEdge, decoration: BoxDecoration( color: Colors.black, image: widget.backgroundImage == null ? null : DecorationImage( image: NetworkImage(widget .backgroundImage ?? 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0c/15/83/36/zona-de-aguas.jpg?w=1200&h=-1&s=1'), fit: BoxFit.fill)), ) : VideoPlayer(_controller!); } else { return Container( width: ScreenUtil().screenWidth, height: ScreenUtil().screenHeight, color: ApplicationColors.black, ); } })), ), ); }
void _handleKey(BuildContext context, LogicalKeyboardKey key) { if (key == LogicalKeyboardKey.enter) { context.read().setIsOverlay(true); } } } `
Could you please provide a full example code?
Sure! Here's the widget. It's called dynamically throughout the app, and when provided with a new URL string, it disposes of the current controller and reinitializes a new one. However, even after being reinitialized, the controller still retains the dimensions from the first screen where it was used. For example, if it's initially displayed full-screen, it keeps that size, even if it's later called in a fixed-size widget, which leads to the video overflowing
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:hotel_iptv/core/constants/application_colors.dart';
import 'package:hotel_iptv/features/videoplayer/application/controllers/video_player_controller.dart';
import 'package:provider/provider.dart';
import 'package:video_player_avplay/video_player.dart';
import 'package:video_player_avplay/video_player_platform_interface.dart';
import '../../../../core/utils/navigation_intents.dart';
class VideoPlayerScreen extends StatefulWidget {
const VideoPlayerScreen({
super.key,
required this.url,
this.looping = false,
this.backgroundImage,
});
final String url;
final bool? looping;
final String? backgroundImage;
@override
State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen>
with SingleTickerProviderStateMixin {
VideoPlayerController? _controller;
@override
void initState() {
super.initState();
if (widget.url.isNotEmpty) {
_initializeController();
}
}
@override
void didUpdateWidget(covariant VideoPlayerScreen oldWidget) {
if (oldWidget.url != widget.url) {
if (_controller != null) {
_disposeController();
if (widget.url.isNotEmpty) {
_initializeController();
}
} else {
if (widget.url.isNotEmpty) {
_initializeController();
}
}
}
super.didUpdateWidget(oldWidget);
}
void _onVideoPLayerStateChanged() {
if (_controller == null) return;
if (_controller!.value.isPlaying) {
context.read<VideoController>().setIsPlaying(true);
} else {
context.read<VideoController>().setIsPlaying(false);
}
}
void _initializeController() {
_controller = VideoPlayerController.network(
playerOptions: {},
widget.url,
formatHint: VideoFormat.dash,
)
..setLooping(widget.looping!)
..initialize().then((_) {
setState(() {
_controller?.play();
});
});
_controller?.addListener(_onVideoPLayerStateChanged);
}
_disposeController() {
_controller?.removeListener(_onVideoPLayerStateChanged);
_controller?.deactivate();
_controller?.dispose();
}
@override
void dispose() {
_disposeController();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.enter): const IntentEnter(),
},
child: Actions(
actions: {
IntentEnter: CallbackAction<IntentEnter>(
onInvoke: (IntentEnter intent) =>
_handleKey(context, LogicalKeyboardKey.enter),
),
},
child: Scaffold(
backgroundColor: ApplicationColors.black,
body: Builder(builder: (context) {
if (widget.url.isNotEmpty && _controller != null) {
return !_controller!.value.isInitialized
? Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Colors.black,
image: widget.backgroundImage == null
? null
: DecorationImage(
image: NetworkImage(widget
.backgroundImage ??
'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0c/15/83/36/zona-de-aguas.jpg?w=1200&h=-1&s=1'),
fit: BoxFit.fill)),
)
: VideoPlayer(_controller!);
} else {
return Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
color: ApplicationColors.black,
);
}
})),
),
);
}
void _handleKey(BuildContext context, LogicalKeyboardKey key) {
if (key == LogicalKeyboardKey.enter) {
context.read<VideoController>().setIsOverlay(true);
}
}
}
I tried adjusting the layout in the plugin, but I encountered continuous overflow issues. Despite calling the widget within constrained boxes, the video player still takes up the entire screen without respecting the parent-child constraints. Here's the code snippet i used:
void _afterFrameLayout(_) {
if (widget.controller.value.isInitialized) {
final Rect currentRect = _getCurrentRect();
if (currentRect != Rect.zero && _playerRect != currentRect) {
_videoPlayerPlatform.setDisplayGeometry(
_playerId,
0,
0,
currentRect.width.toInt(),
currentRect.height.toInt(),
);
_playerRect = currentRect;
}
}
WidgetsBinding.instance.addPostFrameCallback(_afterFrameLayout);
}
Rect _getCurrentRect() {
final Size screenSize = MediaQuery.of(_videoBoxKey.currentContext!).size;
final double pixelRatio = WidgetsBinding.instance.window.devicePixelRatio;
final Size size = screenSize * pixelRatio;
final Offset offset = Offset.zero;
return offset & size;
}
I'm encountering an issue with the video_player_avplay plugin in Flutter-Tizen. I've created a reusable widget for the video player that updates based on the input URL parameter. To achieve this, I use didUpdateWidget to dispose of and reinitialize the player when the URL changes. This player is utilized across multiple screens and listens to a single String provider that updates the URL.
The challenge arises when the player is used on a screen with a fixed-width widget. Upon navigating to a new screen and passing the player's controller as a parameter, the player correctly transitions to fullscreen. However, when I update the URL by setting a new string in the provider, the player unexpectedly resets to the initial size from the previous screen, instead of remaining in fullscreen as intended.
The current setup involves a small screen for previewing a list of videos, with the next route displaying a fullscreen player. The fullscreen player includes a hidden overlay list of videos, which can be displayed on demand for selection.