Skip to content

Commit

Permalink
Merge pull request #31 from arafaysaleem/feature/youtube-player-for-t…
Browse files Browse the repository at this point in the history
…railers

feature(Widgets): add overlay actions to trailer video player
  • Loading branch information
arafaysaleem authored Jun 9, 2021
2 parents b66208c + 27be717 commit b6ced06
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 53 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/PR-open-test-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
channel: 'stable'
- name: Get Pub Dependencies
run: flutter pub get
- name: Run build runner for codegen files
- name: Run Build Runner For Codegen Files
run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Run Dart Analyzer
run: flutter analyze .
- name: Attempt release build generation
- name: Attempt Debug APK Build
run: flutter build apk --debug
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.inceptrafay.ez_ticketz_app"
minSdkVersion 16
minSdkVersion 17
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
166 changes: 116 additions & 50 deletions lib/views/screens/trailer_screen.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import 'package:auto_route/auto_route.dart';
import 'package:better_player/better_player.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Helpers
import '../../helper/utils/constants.dart';
import '../../helper/extensions/context_extensions.dart';

//Providers
import '../../providers/movies_provider.dart';
import '../widgets/trailer/overlay_back_button.dart';

//Widgets
import '../widgets/trailer/overlay_black_header.dart';
import '../widgets/trailer/overlay_movie_title.dart';
import '../widgets/trailer/overlay_play_pause_button.dart';

class TrailerScreen extends StatefulWidget {
const TrailerScreen();
Expand All @@ -21,18 +25,26 @@ class TrailerScreen extends StatefulWidget {
class _TrailerScreenState extends State<TrailerScreen> {
late final BetterPlayerController _betterPlayerController;

final _controlsConfiguration = const BetterPlayerControlsConfiguration(
overflowModalTextColor: Constants.textGreyColor,
overflowMenuIconsColor: Constants.textGreyColor,
overflowModalColor: Constants.scaffoldGreyColor,
progressBarPlayedColor: Constants.primaryColor,
progressBarHandleColor: Constants.primaryColor,
backgroundColor: Colors.black38,
controlBarColor: Colors.black54,
enablePip: false,
enableSubtitles: false,
static const _controlsConfiguration = BetterPlayerControlsConfiguration(
overflowModalTextColor: Constants.textGreyColor,
overflowMenuIconsColor: Constants.textGreyColor,
overflowModalColor: Constants.scaffoldGreyColor,
progressBarPlayedColor: Constants.primaryColor,
progressBarHandleColor: Constants.primaryColor,
progressBarBufferedColor: Color(0x72ed0000),
backgroundColor: Colors.black38,
controlBarColor: Colors.black54,
loadingColor: Constants.redColor,
enablePip: false,
enableSubtitles: false,
controlBarHeight: 60,
);

static const _exitFullScreenOrientations = <DeviceOrientation>[
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
];

@override
void initState() {
super.initState();
Expand All @@ -41,16 +53,14 @@ class _TrailerScreenState extends State<TrailerScreen> {
fullScreenAspectRatio: 16 / 9,
looping: false,
autoPlay: true,
fit: BoxFit.cover,
allowedScreenSleep: false,
deviceOrientationsAfterFullScreen: [
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
],
systemOverlaysAfterFullScreen: [SystemUiOverlay.top],
fullScreenByDefault: true,
allowedScreenSleep: false,
autoDetectFullscreenDeviceOrientation: true,
fit: BoxFit.cover,
controlsConfiguration: _controlsConfiguration,
deviceOrientationsAfterFullScreen: _exitFullScreenOrientations,
systemOverlaysAfterFullScreen: [SystemUiOverlay.top],
routePageBuilder: _fullScreenRouteBuilder,
errorBuilder: _buildErrorWidget,
);
final trailerUrl = context.read(selectedMovieProvider).state.trailerUrl;
Expand All @@ -64,44 +74,100 @@ class _TrailerScreenState extends State<TrailerScreen> {
);
}

/// List of overlay widgets for the player that add useful functionalities,
/// like Back Navigation, Play/Pause ability or Movie Title.
List<Widget> buildOverlayWidgets() {
return [
//Black overlay header
Align(
alignment: Alignment.topCenter,
child: OverlayBlackHeader(
betterPlayerController: _betterPlayerController,
),
),

//Movie title
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 20),
child: OverlayMovieTitle(
betterPlayerController: _betterPlayerController,
),
),
),

//Back button
Align(
alignment: Alignment.topLeft,
child: OverlayBackButton(
betterPlayerController: _betterPlayerController,
),
),

//Play/Pause Button
Align(
alignment: _betterPlayerController.isFullScreen
? Alignment.center
: Alignment.topCenter,
child: Padding(
padding: _betterPlayerController.isFullScreen
? const EdgeInsets.all(0)
: const EdgeInsets.only(top: 105),
child: OverlayPlayPauseButton(
betterPlayerController: _betterPlayerController,
),
),
),
];
}

/// Defines the builder for the new route that gets pushed on
/// the fullscreen mode.
Widget _fullScreenRouteBuilder(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
BetterPlayerControllerProvider provider,
) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: AnimatedBuilder(
animation: animation,
builder: (ctx, _) => Stack(
children: [
//Video Player
Container(
alignment: Alignment.center,
child: provider,
),

//Overlay widgets
...buildOverlayWidgets()
],
),
),
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
child: Stack(
children: [
const SizedBox(height: 20),

//Back and title
Row(
children: [
const SizedBox(width: 15),
GestureDetector(
child: const Icon(Icons.arrow_back_sharp, size: 26),
onTap: () {
context.router.pop();
},
),

const SizedBox(width: 20),

//Movie Title
Expanded(
child: Consumer(
builder: (_, watch, __) {
final title = watch(selectedMovieProvider).state.title;
return Text(
title,
maxLines: 1,
style: context.headline3.copyWith(fontSize: 22),
);
},
),
),
],
//Video Player
Positioned(
top: 5,
right: 0,
left: 0,
child: BetterPlayer(controller: _betterPlayerController),
),

Expanded(child: BetterPlayer(controller: _betterPlayerController)),
//Overlay widgets
...buildOverlayWidgets(),
],
),
),
Expand Down
65 changes: 65 additions & 0 deletions lib/views/widgets/trailer/overlay_back_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:better_player/better_player.dart';
import 'package:auto_route/auto_route.dart';

class OverlayBackButton extends StatefulWidget {
final BetterPlayerController betterPlayerController;

const OverlayBackButton({
Key? key,
required this.betterPlayerController,
}) : super(key: key);

@override
_OverlayBackButtonState createState() => _OverlayBackButtonState();
}

class _OverlayBackButtonState extends State<OverlayBackButton> {
bool _isVisible = false;

BetterPlayerController get _betterPlayerController => widget.betterPlayerController;

@override
void initState() {
super.initState();
_betterPlayerController.addEventsListener(_handlePlayerEventChanges);
}

/// Listens to all events sent by [_betterPlayerController]
/// and handles them with the appropriate response.
void _handlePlayerEventChanges(BetterPlayerEvent event) {
final eventType = event.betterPlayerEventType;
//handle events if initialized
final controlsVisible = eventType == BetterPlayerEventType.controlsVisible;
final controlsHidden = eventType == BetterPlayerEventType.controlsHidden;
if (controlsVisible || controlsHidden) { //if overlay controls toggled
setState(() {
_isVisible = controlsVisible;
});
}
}

@override
Widget build(BuildContext context) {
if (!_isVisible) {
return const SizedBox.shrink();
}
return InkWell(
onTap: () => context.router.pop(),
child: const Padding(
padding: EdgeInsets.all(15),
child: Icon(
Icons.arrow_back_sharp,
color: Colors.white,
size: 28,
),
),
);
}

@override
void dispose() {
_betterPlayerController.removeEventsListener(_handlePlayerEventChanges);
super.dispose();
}
}
62 changes: 62 additions & 0 deletions lib/views/widgets/trailer/overlay_black_header.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:better_player/better_player.dart';

class OverlayBlackHeader extends StatefulWidget {
final BetterPlayerController betterPlayerController;

const OverlayBlackHeader({
Key? key,
required this.betterPlayerController,
}) : super(key: key);

@override
_OverlayBlackHeaderState createState() => _OverlayBlackHeaderState();
}

class _OverlayBlackHeaderState extends State<OverlayBlackHeader> {
bool _isVisible = false;

BetterPlayerController get _betterPlayerController => widget.betterPlayerController;

@override
void initState() {
super.initState();
_betterPlayerController.addEventsListener(_handlePlayerEventChanges);
}

/// Listens to all events sent by [_betterPlayerController]
/// and handles them with the appropriate response.
void _handlePlayerEventChanges(BetterPlayerEvent event) {
final eventType = event.betterPlayerEventType;
//handle events if initialized
final controlsVisible = eventType == BetterPlayerEventType.controlsVisible;
final controlsHidden = eventType == BetterPlayerEventType.controlsHidden;
if (controlsVisible || controlsHidden) { //if overlay controls toggled
setState(() {
_isVisible = controlsVisible;
});
}
}

@override
Widget build(BuildContext context) {
if (!_isVisible) {
return const SizedBox.shrink();
}
return const IgnorePointer(
child: SizedBox(
width: double.infinity,
height: 55,
child: ColoredBox(
color: Colors.black54,
),
),
);
}

@override
void dispose() {
_betterPlayerController.removeEventsListener(_handlePlayerEventChanges);
super.dispose();
}
}
Loading

0 comments on commit b6ced06

Please sign in to comment.