Add stream request & viewercount on video overlay
This commit is contained in:
parent
caf30c171e
commit
a6688a992c
5 changed files with 177 additions and 69 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,10 @@ class VideoChat extends StatelessWidget {
|
|||
title: title,
|
||||
userName: userName,
|
||||
userLogin: userLogin,
|
||||
videoStore: VideoStore(),
|
||||
videoStore: VideoStore(
|
||||
userLogin: userLogin,
|
||||
authStore: context.read<AuthStore>(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue