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

Video Player Resets to Previous Screen Size on URL Update in Fullscreen Mode, On Tizen-TV-Platform #726

Open
mwesigwadi opened this issue Sep 2, 2024 · 6 comments
Assignees

Comments

@mwesigwadi
Copy link

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.

@mwesigwadi mwesigwadi changed the title Video Player Resets to Previous Screen Size on URL Update in Fullscreen Mode Video Player Resets to Previous Screen Size on URL Update in Fullscreen Mode, On Tizen-TV-Platform Sep 2, 2024
@mwesigwadi
Copy link
Author

Any help on this ?

@xiaowei-guan
Copy link
Contributor

@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:

  1. Register addPostFrameCallback when init widget state:
WidgetsBinding.instance.addPostFrameCallback(_afterFrameLayout);
  1. Set the native player's geometry when getting post frame callback
 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);
  }
  1. You should keep only one VideoPlayer widget to hold the Video Controller.
  2. if you change the URL, you need re-create Video controller.

@mwesigwadi
Copy link
Author

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.

@mwesigwadi
Copy link
Author

mwesigwadi commented Sep 3, 2024

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

@xiaowei-guan
Copy link
Contributor

xiaowei-guan commented Sep 4, 2024

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?

@mwesigwadi
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants