Skip to content

Commit

Permalink
Use youtube_player_flutter
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Nguyen committed Aug 2, 2020
1 parent 404575f commit f9473da
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 248 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ There is an issue with the [`PageController`](https://api.flutter.dev/flutter/wi
This might be a bug concerning the use of [`CustomScrollView`](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html) under a [`PageView`](https://api.flutter.dev/flutter/widgets/PageView-class.html), I'm not exactly sure.
It may be worth checking out the [`SliverLayoutBuilder`](https://api.flutter.dev/flutter/widgets/SliverLayoutBuilder-class.html) class.

**UPDATE:** It seems other people have been having the same issue. See issue [#61156](https://github.com/flutter/flutter/issues/61156).

### Embedded Youtube video (Android)

The audio from the embedded Youtube video persists if the app is not closed and the user decides to go out of the app or turns off their display.
Expand Down
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.izoneapp"
minSdkVersion 16
minSdkVersion 17
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
Binary file not shown.
3 changes: 1 addition & 2 deletions lib/controllers/app_page_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ class _AppPageViewState extends State<AppPageView> {

@override
Widget build(BuildContext context) {
_page = AppPage.LYRICS;
List<AppPageInfo> _appPages = AppPages.pages(
final List<AppPageInfo> _appPages = AppPages.pages(
context,
_pageController,
_appBarController,
Expand Down
5 changes: 4 additions & 1 deletion lib/controllers/scrollable_app_bar_scroll_behavior.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import 'package:flutter/material.dart';
class ScrollableAppBarScrollBehavior extends ScrollBehavior {
@override
Widget buildViewportChrome(
BuildContext context, Widget child, AxisDirection direction) {
BuildContext context,
Widget child,
AxisDirection direction,
) {
return child;
}
}
8 changes: 3 additions & 5 deletions lib/data/app_pages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class AppPageInfo {
final Widget icon;
final Widget page;

AppPageInfo({
const AppPageInfo({
this.title,
this.longTitle,
this.index,
Expand Down Expand Up @@ -80,7 +80,7 @@ class AppPages {
) {
appBarController.animateTo(
page.index * 50.0,
duration: Duration(milliseconds: 1000),
duration: const Duration(seconds: 1),
curve: Curves.fastLinearToSlowEaseIn,
);
}
Expand All @@ -92,9 +92,7 @@ class AppPages {
) {
pageController.animateToPage(
page.index,
duration: const Duration(
milliseconds: 1000,
),
duration: const Duration(seconds: 1),
curve: Curves.fastLinearToSlowEaseIn,
);
scrollAppBarToPage(page, appBarController);
Expand Down
248 changes: 45 additions & 203 deletions lib/pages/level_1/page_youtube_video_list.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:izoneapp/data/youtube_video.dart';
import 'package:izoneapp/pages/page_view_youtube_video.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';

class YoutubeVideoListPage extends StatefulWidget {
const YoutubeVideoListPage({Key key, this.videos}) : super(key: key);
Expand All @@ -19,68 +12,27 @@ class YoutubeVideoListPage extends StatefulWidget {
}

class YoutubeVideoListPageState extends State<YoutubeVideoListPage> {
String _currentVideoId = '';
StreamController<String> _videoController;

@override
void initState() {
super.initState();
_videoController = StreamController();
_videoController.stream.listen((videoId) {
setState(() {
_currentVideoId = videoId;
});
});
}

@override
void dispose() {
_videoController.close();
super.dispose();
}

@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations(
[
DeviceOrientation.values
.elementAt(MediaQuery.of(context).orientation.index),
],
);
return Scaffold(
body: LayoutBuilder(builder: (context, constraints) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 240,
collapsedHeight: 240,
floating: true,
pinned: true,
elevation: 0,
flexibleSpace: _VideoPlaceholder(videoId: _currentVideoId),
),
_VideoList(
videoId: _currentVideoId,
videos: widget.videos,
videoController: _videoController,
),
],
);
} else {
return Container(
child: Row(
children: [
Expanded(
child: _VideoPlaceholder(videoId: _currentVideoId),
),
Expanded(
child: CustomScrollView(
slivers: [
_VideoList(
videoId: _currentVideoId,
videos: widget.videos,
videoController: _videoController,
),
],
),
Expand All @@ -89,182 +41,72 @@ class YoutubeVideoListPageState extends State<YoutubeVideoListPage> {
),
);
}
}),
},
);
}
}

class _VideoPlaceholder extends StatelessWidget {
final String videoId;

const _VideoPlaceholder({Key key, @required this.videoId}) : super(key: key);

@override
Widget build(BuildContext context) {
try {
if ((Platform.isAndroid || Platform.isIOS) && videoId.isNotEmpty) {
return HtmlWidget(
'<iframe width="560" height="315" src="https://www.youtube.com/embed/$videoId" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
webView: true,
);
}
} catch (e) {}
return Container(
padding: const EdgeInsets.all(12.0),
color: Theme.of(context).primaryColor,
child: Center(
child: RichText(
text: TextSpan(
style: TextStyle(
color: Theme.of(context).textTheme.bodyText1.color,
),
children: [
TextSpan(
text: 'Select a video to watch.\n\n',
),
TextSpan(
text:
'Press the YouTube icon to watch the video in the YouTube app.\n\n',
),
TextSpan(
text:
'Videos highlighted in red will open the YouTube app due to content distribution restrictions.',
style: TextStyle(
color: Color.lerp(
Colors.red,
Theme.of(context).textTheme.bodyText1.color,
0.5,
),
fontWeight: FontWeight.bold,
),
),
],
),
),
),
);
}
}

class _VideoList extends StatefulWidget {
final String videoId;
class _VideoList extends StatelessWidget {
final List<YoutubeVideo> videos;
final StreamController<String> videoController;

const _VideoList({
Key key,
@required this.videoId,
@required this.videos,
@required this.videoController,
}) : super(key: key);

@override
State<StatefulWidget> createState() => _VideoListState();
}

class _VideoListState extends State<_VideoList> {
void _toggleFullscreen(int index) {
SystemChrome.setEnabledSystemUIOverlays([]);

Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
// Stop current video and switch to fullscreen
widget.videoController.add('');

return ViewYoutubeVideoPage(
youtubeUrl: widget.videos.elementAt(index).url,
);
},
),
);
}

@override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
clipBehavior: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
).clipBehavior,
color: widget.videoId == widget.videos.elementAt(index).youtubeId
? Theme.of(context).backgroundColor
: Theme.of(context).cardColor,
child: InkWell(
onTap: () async {
if (!widget.videos.elementAt(index).restricted) {
try {
if ((Platform.isAndroid || Platform.isIOS)) {
widget.videoController
.add(widget.videos.elementAt(index).youtubeId);
} else if (await canLaunch(
widget.videos.elementAt(index).url)) {
launch(widget.videos.elementAt(index).url);
}
} catch (e) {
// Web
if (await canLaunch(widget.videos.elementAt(index).url)) {
launch(widget.videos.elementAt(index).url);
}
}
// Restricted
} else if (await canLaunch(
widget.videos.elementAt(index).url)) {
launch(widget.videos.elementAt(index).url);
} else {
throw 'Could not launch ${widget.videos.elementAt(index).url}.';
}
},
splashFactory: InkRipple.splashFactory,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
widget.videos.elementAt(index).thumbnail,
return Container(
height: 230.0,
child: Card(
clipBehavior: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
).clipBehavior,
child: InkWell(
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => YoutubePlayerBuilder(
player: YoutubePlayer(
controller: YoutubePlayerController(
initialVideoId: videos.elementAt(index).youtubeId,
),
),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Theme.of(context).primaryColor.withOpacity(0.2),
BlendMode.dstATop,
builder: (context, player) {
return player;
},
),
)),
splashFactory: InkRipple.splashFactory,
child: Container(
alignment: Alignment.bottomCenter,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
videos.elementAt(index).thumbnail,
),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Theme.of(context).primaryColor.withOpacity(0.2),
BlendMode.dstATop,
),
),
),
child: ListTile(
title: Text('${videos.elementAt(index).title}'),
subtitle: Text(videos.elementAt(index).subtitle),
trailing: Text(
MaterialLocalizations.of(context)
.formatCompactDate(videos.elementAt(index).date),
),
),
),
child: ListTile(
leading:
widget.videoId == widget.videos.elementAt(index).youtubeId
? IconButton(
icon: const FaIcon(FontAwesomeIcons.expand),
onPressed: () => _toggleFullscreen(index),
)
: IconButton(
icon: FaIcon(
FontAwesomeIcons.youtube,
color: widget.videos.elementAt(index).restricted
? Colors.red
: IconTheme.of(context).color,
),
onPressed: () async {
if (await canLaunch(
widget.videos.elementAt(index).url)) {
launch(widget.videos.elementAt(index).url);
} else {
throw 'Unable to open video "${widget.videos.elementAt(index).url}"';
}
},
),
title: Text('${widget.videos.elementAt(index).title}'),
subtitle: Text(widget.videos.elementAt(index).subtitle),
trailing: Text(MaterialLocalizations.of(context)
.formatCompactDate(widget.videos.elementAt(index).date)),
),
),
),
);
},
childCount: widget.videos.length,
childCount: videos.length,
),
);
}
Expand Down
Loading

0 comments on commit f9473da

Please sign in to comment.