Skip to content

Commit

Permalink
Add WHEP player for Waveguide support (#65)
Browse files Browse the repository at this point in the history
* Add WHEP player for Waveguide support

* Update live URL

* Adding backend metadata and removing submodule
  • Loading branch information
clone1018 authored Jan 13, 2023
1 parent 42a1648 commit 711aab2
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 19 deletions.
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
connectivity_plus: 5f0eb61093bec56935f21a1699dd2758bc895587
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_web_auth: 09a0abd245f1a07a3ff4dcf1247a048d89ee12a9
flutter_webrtc: 3fd57d5bd9b6ce85d12d37f6f68d97be3b80509f
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,7 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>
1 change: 1 addition & 0 deletions lib/blocs/repos/channel_list_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class ChannelListBloc extends Bloc<ChannelListEvent, ChannelListState> {
return Channel(
id: int.parse(json['node']['id']),
title: json['node']['title'] as String,
backend: json['node']['backend'] as String,
chatBackgroundUrl: json['node']['chatBgUrl'] as String,
thumbnail: json['node']['stream']['thumbnailUrl'] as String,
username: json['node']['streamer']['username'] as String,
Expand Down
10 changes: 9 additions & 1 deletion lib/components/StreamTitle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ class _StreamTitleState extends State<StreamTitle> {
_buildSubcategoryTag(),
_buildTags(context),
_buildLanguageTag(context),
_buildMatureTag()
_buildMatureTag(),
_buildBackendTag(),
],
),
),
Expand Down Expand Up @@ -143,6 +144,13 @@ class _StreamTitleState extends State<StreamTitle> {
);
}

Widget _buildBackendTag() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [Text("Backend"), Chip(label: Text(widget.channel.backend))],
);
}

Widget _buildMetadataItem(String label, String content) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
Expand Down
178 changes: 178 additions & 0 deletions lib/components/WHEPPlayer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import 'dart:developer';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:glimesh_app/components/Loading.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:glimesh_app/models.dart';
import 'package:wakelock/wakelock.dart';
import 'package:http/http.dart' as http;

class WHEPPlayer extends StatefulWidget {
final Channel channel;
final String edgeUrl;

const WHEPPlayer({Key? key, required this.channel, required this.edgeUrl})
: super(key: key);

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

class _WHEPPlayerState extends State<WHEPPlayer> {
RTCPeerConnection? pc;
RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer();

bool _loading = true;
bool _errored = false;
bool _fatalErrored = false;
String _errorMessage = "";
int _seconds = 0;

@override
void didChangeDependencies() async {
super.didChangeDependencies();
await _remoteRenderer.initialize();
}

initConnection() async {
log("Init peer connection");
pc = await createPeerConnection({});
if (pc == null) {
return;
}
pc!.onTrack = (event) {
_remoteRenderer.srcObject = event.streams[0];
setState(() {
_fatalErrored = false;
_errored = false;
_loading = false;
});
};

var endpoint =
Uri.https("live.glimesh.tv", "v1/whep/endpoint/${widget.channel.id}");
log("POST ${endpoint.toString()}");
var response = await http.post(
endpoint,
headers: {"Accept": "application/sdp"},
body: "",
);
if (response.statusCode == 307) {
var location = response.headers["location"]!;
log("POST ${location}");
// Kinda sucks that this is hardcoded, need to loop it... One hop for now only.
var response2 = await http.post(
Uri.parse(location),
headers: {"Accept": "application/sdp"},
body: "",
);

log("${response2.statusCode} ${response2.body}");
response = response2;
}
log("${response.statusCode} ${response.body}");
if (response.statusCode != 201 ||
response.headers.containsKey("location") == false) {
if (mounted) {
setState(() {
_fatalErrored = true;
_loading = false;
_errorMessage = "WebRTC failed to negotiate offer from server.";
});
}
return;
}

var offer = response.body;
var sdp = RTCSessionDescription(offer, "offer");
await pc!.setRemoteDescription(sdp);
log("after setRemoteDescription");

var answer = await pc!.createAnswer();
await pc!.setLocalDescription(answer);
log("after setLocalDescription");

var answerEndpoint = Uri.parse(response.headers["location"]!);
log("PATCH ${answerEndpoint}");
var answerResponse = await http.patch(
answerEndpoint,
headers: {"Accept": "application/sdp"},
body: answer.sdp,
);
log("${answerResponse.statusCode} ${answerResponse.body}");
if (answerResponse.statusCode != 204) {
if (mounted) {
setState(() {
_fatalErrored = true;
_loading = false;
_errorMessage = "WebRTC failed to negotiate answer with server.";
});
}
return;
}
}

@override
void initState() {
super.initState();
initConnection();

Wakelock.enable();
}

@override
void dispose() {
if (Wakelock.enabled == true) {
Wakelock.disable();
}

if (pc != null) {
pc!.close();
}

_remoteRenderer.srcObject = null;
_remoteRenderer.dispose();

super.dispose();
}

@override
Widget build(BuildContext context) {
return _fatalErrored
? Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: Text(
"Error loading video, please disable any VPNs or other software that could negatively impact WebRTC video and then try again. $_errorMessage",
textAlign: TextAlign.center,
),
),
)
: Stack(
children: [
// Background layers that the video will take over when properly loaded
_loading
? Image.network(widget.channel.thumbnail)
: Padding(padding: EdgeInsets.zero),
Container(
decoration: BoxDecoration(color: Colors.black45),
child: Loading("Loading Video"),
),
_errored
? Center(
child: Text(
"Error loading video, please try again. $_errorMessage"),
)
: RTCVideoView(
_remoteRenderer,
objectFit:
RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
),
_loading
? Loading("Loading Video")
: Padding(padding: EdgeInsets.zero),
],
);
}
}
3 changes: 3 additions & 0 deletions lib/graphql/queries/channels.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ query LiveChannels($categorySlug: String!) {
node {
id
title
backend
chatBgUrl
language
matureContent
Expand Down Expand Up @@ -41,6 +42,7 @@ query GetMyself {
node {
id
title
backend
chatBgUrl
language
matureContent
Expand Down Expand Up @@ -76,6 +78,7 @@ query GetHomepageChannels {
node {
id
title
backend
chatBgUrl
language
matureContent
Expand Down
2 changes: 2 additions & 0 deletions lib/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class Channel {
Channel({
required this.id,
required this.title,
required this.backend,
required this.thumbnail,
required this.chatBackgroundUrl,
required this.username,
Expand All @@ -162,6 +163,7 @@ class Channel {

final int id;
final String title;
final String backend;
final String thumbnail;
final String chatBackgroundUrl;

Expand Down
5 changes: 4 additions & 1 deletion lib/screens/ChannelScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:glimesh_app/blocs/repos/channel_bloc.dart';
import 'package:glimesh_app/components/Chat.dart';
import 'package:glimesh_app/components/FTLPlayer.dart';
import 'package:glimesh_app/components/WHEPPlayer.dart';
import 'package:glimesh_app/components/StreamTitle.dart';
import 'package:glimesh_app/components/Loading.dart';
import 'package:glimesh_app/components/MatureWarning.dart';
Expand Down Expand Up @@ -91,7 +92,9 @@ class _ChannelScreenState extends State<ChannelScreen> {
InkWell(
child: AspectRatio(
aspectRatio: 16 / 9,
child: FTLPlayer(channel: channel, edgeUrl: edgeRoute.url),
child: channel.backend == "ftl"
? FTLPlayer(channel: channel, edgeUrl: edgeRoute.url)
: WHEPPlayer(channel: channel, edgeUrl: edgeRoute.url),
),
onTap: () {
setState(() {
Expand Down
Loading

0 comments on commit 711aab2

Please sign in to comment.