Add stream request & viewercount on video overlay

This commit is contained in:
tommyxchow 2021-11-24 04:39:05 -05:00
parent caf30c171e
commit a6688a992c
5 changed files with 177 additions and 69 deletions

View file

@ -187,6 +187,24 @@ class Twitch {
}
}
/// Returns the stream info given the user login.
static Future<StreamTwitch?> getStream({required String userLogin, required Map<String, String>? headers}) async {
final uri = Uri.parse('https://api.twitch.tv/helix/streams?user_login=$userLogin');
final response = await http.get(uri, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final data = decoded['data'] as List;
if (data.isEmpty) return null;
return StreamTwitch.fromJson(data.first);
} else {
debugPrint('Failed to update top streams');
}
}
/// Returns a user's info given their login name.
static Future<UserTwitch?> getUser({required String userLogin, required Map<String, String>? headers}) async {
final response = await http.get(Uri.parse('https://api.twitch.tv/helix/users?login=$userLogin'), headers: headers);

View file

@ -3,6 +3,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/channel/video/video_store.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class VideoOverlay extends StatelessWidget {
@ -31,75 +32,90 @@ class VideoOverlay extends StatelessWidget {
color: Colors.black.withOpacity(0.5),
child: IgnorePointer(
ignoring: !videoStore.menuVisible,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Stack(
children: [
Row(
Column(
children: [
IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () => Navigator.of(context).pop(),
),
const Spacer(),
IconButton(
icon: const Icon(
Icons.settings,
color: Colors.white,
),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return Settings(settingsStore: context.read<SettingsStore>());
},
);
},
),
],
),
IconButton(
icon: videoStore.paused
? const Icon(
Icons.play_arrow,
color: Colors.white,
)
: const Icon(
Icons.pause,
color: Colors.white,
Row(
children: [
IconButton(
icon: Icon(
Icons.adaptive.arrow_back,
color: Colors.white,
),
onPressed: () => Navigator.of(context).pop(),
),
onPressed: videoStore.handlePausePlay,
),
Row(
children: [
const SizedBox(width: 10),
Text(
userName,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
const Spacer(),
IconButton(
icon: const Icon(
Icons.settings,
color: Colors.white,
),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return Settings(settingsStore: context.read<SettingsStore>());
},
);
},
),
],
),
const Spacer(),
IconButton(
icon: const Icon(
Icons.picture_in_picture_alt_rounded,
color: Colors.white,
),
onPressed: videoStore.enterPictureInPicture,
),
IconButton(
icon: const Icon(
Icons.fullscreen,
color: Colors.white,
),
onPressed: videoStore.requestFullscreen,
Row(
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userName,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 5.0),
if (videoStore.streamInfo != null) Text('${NumberFormat().format(videoStore.streamInfo?.viewerCount)} viewers'),
],
),
),
const Spacer(),
IconButton(
icon: const Icon(
Icons.picture_in_picture_alt_rounded,
color: Colors.white,
),
onPressed: videoStore.enterPictureInPicture,
),
IconButton(
icon: const Icon(
Icons.fullscreen,
color: Colors.white,
),
onPressed: videoStore.requestFullscreen,
)
],
)
],
)
),
Center(
child: IconButton(
icon: videoStore.paused
? const Icon(
Icons.play_arrow,
color: Colors.white,
)
: const Icon(
Icons.pause,
color: Colors.white,
),
onPressed: videoStore.handlePausePlay,
),
),
],
),
),

View file

@ -1,5 +1,8 @@
import 'dart:async';
import 'dart:io';
import 'package:frosty/api/twitch_api.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/models/stream.dart';
import 'package:mobx/mobx.dart';
import 'package:webview_flutter/webview_flutter.dart';
@ -12,16 +15,25 @@ abstract class _VideoStoreBase with Store {
late Timer timer;
_VideoStoreBase() {
timer = Timer(const Duration(seconds: 3), () => menuVisible = false);
}
@observable
var menuVisible = true;
@observable
var paused = false;
@observable
StreamTwitch? streamInfo;
final String userLogin;
final AuthStore authStore;
_VideoStoreBase({required this.userLogin, required this.authStore}) {
timer = Timer(const Duration(seconds: 3), () => menuVisible = false);
updateStreamInfo();
}
@action
void handlePausePlay() {
if (paused) {
controller.runJavascript('document.getElementsByTagName("video")[0].play();');
@ -32,7 +44,8 @@ abstract class _VideoStoreBase with Store {
paused = !paused;
}
void handleVideoTap() async {
@action
void handleVideoTap() {
if (menuVisible) {
timer.cancel();
@ -44,6 +57,15 @@ abstract class _VideoStoreBase with Store {
});
menuVisible = true;
updateStreamInfo();
}
}
@action
Future<void> updateStreamInfo() async {
final updatedStreamInfo = await Twitch.getStream(userLogin: userLogin, headers: authStore.headersTwitch);
if (updatedStreamInfo != null) {
streamInfo = updatedStreamInfo;
}
}

View file

@ -39,11 +39,60 @@ mixin _$VideoStore on _VideoStoreBase, Store {
});
}
final _$streamInfoAtom = Atom(name: '_VideoStoreBase.streamInfo');
@override
StreamTwitch? get streamInfo {
_$streamInfoAtom.reportRead();
return super.streamInfo;
}
@override
set streamInfo(StreamTwitch? value) {
_$streamInfoAtom.reportWrite(value, super.streamInfo, () {
super.streamInfo = value;
});
}
final _$updateStreamInfoAsyncAction =
AsyncAction('_VideoStoreBase.updateStreamInfo');
@override
Future<void> updateStreamInfo() {
return _$updateStreamInfoAsyncAction.run(() => super.updateStreamInfo());
}
final _$_VideoStoreBaseActionController =
ActionController(name: '_VideoStoreBase');
@override
void handlePausePlay() {
final _$actionInfo = _$_VideoStoreBaseActionController.startAction(
name: '_VideoStoreBase.handlePausePlay');
try {
return super.handlePausePlay();
} finally {
_$_VideoStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
void handleVideoTap() {
final _$actionInfo = _$_VideoStoreBaseActionController.startAction(
name: '_VideoStoreBase.handleVideoTap');
try {
return super.handleVideoTap();
} finally {
_$_VideoStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
String toString() {
return '''
menuVisible: ${menuVisible},
paused: ${paused}
paused: ${paused},
streamInfo: ${streamInfo}
''';
}
}

View file

@ -37,7 +37,10 @@ class VideoChat extends StatelessWidget {
title: title,
userName: userName,
userLogin: userLogin,
videoStore: VideoStore(),
videoStore: VideoStore(
userLogin: userLogin,
authStore: context.read<AuthStore>(),
),
),
);
}