Skip to content

Commit

Permalink
External stream (#174)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdlukaa committed Nov 9, 2023
2 parents 0f9737a + f1d0eb3 commit fdb2827
Show file tree
Hide file tree
Showing 20 changed files with 612 additions and 179 deletions.
35 changes: 22 additions & 13 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bluecherrydvr">
<application
<application
android:label="Bluecherry"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
Expand All @@ -18,16 +18,25 @@
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK"/>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- https://developer.android.com/training/app-links/verify-android-applinks -->
<!-- Make sure you explicitly set android:autoVerify to "true". -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="rtsp" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
Expand All @@ -36,9 +45,9 @@
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_linked_camera" />
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_linked_camera" />
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
</manifest>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>
2 changes: 1 addition & 1 deletion lib/l10n/app_pt.arb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"@@locale": "pt",
"welcome": "Bem vindo!",
"welcomeDescription": "Bem vindo ao Bluecherry Surveillance DVR!\nVamos conectar ao seu servidor DVR em un instante.",
"welcomeDescription": "Sea bem vindo ao Bluecherry Surveillance DVR!\nVamos conectar ao seu servidor DVR em um instante!",
"configure": "Configure um Servidor DVR",
"configureDescription": "Configure uma conex茫o com seu servidor DVR remoto",
"hostname": "Hostname",
Expand Down
25 changes: 25 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import 'package:bluecherry_client/firebase_messaging_background_handler.dart';
import 'package:bluecherry_client/models/device.dart';
import 'package:bluecherry_client/models/event.dart';
import 'package:bluecherry_client/models/layout.dart';
import 'package:bluecherry_client/models/server.dart';
import 'package:bluecherry_client/providers/desktop_view_provider.dart';
import 'package:bluecherry_client/providers/downloads_provider.dart';
import 'package:bluecherry_client/providers/events_playback_provider.dart';
Expand All @@ -33,6 +34,7 @@ import 'package:bluecherry_client/providers/mobile_view_provider.dart';
import 'package:bluecherry_client/providers/server_provider.dart';
import 'package:bluecherry_client/providers/settings_provider.dart';
import 'package:bluecherry_client/providers/update_provider.dart';
import 'package:bluecherry_client/utils/app_links.dart' as app_links;
import 'package:bluecherry_client/utils/methods.dart';
import 'package:bluecherry_client/utils/storage.dart';
import 'package:bluecherry_client/utils/theme.dart';
Expand Down Expand Up @@ -162,6 +164,10 @@ class _UnityAppState extends State<UnityApp> with WidgetsBindingObserver {
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);

app_links.register('rtsp');
app_links.init();
app_links.listen();
}

@override
Expand Down Expand Up @@ -292,6 +298,25 @@ class _UnityAppState extends State<UnityApp> with WidgetsBindingObserver {
);
}

if (settings.name == '/rtsp') {
final url = settings.arguments as String;
return MaterialPageRoute(
settings: RouteSettings(
name: '/rtsp',
arguments: url,
),
builder: (context) {
return LivePlayer.fromUrl(
url: url,
device: Device.dump(
name: 'External stream',
url: url,
)..server = Server.dump(name: url),
);
},
);
}

return null;
},
),
Expand Down
17 changes: 14 additions & 3 deletions lib/models/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'dart:convert';
import 'package:bluecherry_client/models/server.dart';
import 'package:bluecherry_client/providers/server_provider.dart';
import 'package:bluecherry_client/utils/extensions.dart';
import 'package:bluecherry_client/widgets/device_grid/desktop/external_stream.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;

Expand Down Expand Up @@ -50,6 +51,8 @@ class Device {

final String? url;

final MatrixType matrixType;

/// Creates a device.
Device(
this.name,
Expand All @@ -60,16 +63,18 @@ class Device {
this.server, {
this.hasPTZ = false,
this.url,
this.matrixType = MatrixType.t16,
});

Device.dump({
this.name = 'device',
this.id = 0,
this.id = -1,
this.status = true,
this.resolutionX = 640,
this.resolutionY = 480,
this.hasPTZ = false,
this.url,
this.matrixType = MatrixType.t16,
}) : server = Server.dump();

String get uri => 'live/$id';
Expand Down Expand Up @@ -220,7 +225,8 @@ class Device {
resolutionX == other.resolutionX &&
resolutionY == other.resolutionY &&
hasPTZ == other.hasPTZ &&
url == other.url;
url == other.url &&
matrixType == other.matrixType;
}

@override
Expand All @@ -231,7 +237,8 @@ class Device {
resolutionX.hashCode ^
resolutionY.hashCode ^
hasPTZ.hashCode ^
url.hashCode;
url.hashCode ^
matrixType.hashCode;

Device copyWith({
String? name,
Expand All @@ -242,6 +249,7 @@ class Device {
Server? server,
bool? hasPTZ,
String? url,
MatrixType? matrixType,
}) =>
Device(
name ?? this.name,
Expand All @@ -252,6 +260,7 @@ class Device {
server ?? this.server,
hasPTZ: hasPTZ ?? this.hasPTZ,
url: url ?? this.url,
matrixType: matrixType ?? this.matrixType,
);

Map<String, dynamic> toJson() {
Expand All @@ -264,6 +273,7 @@ class Device {
'server': server.toJson(devices: false),
'hasPTZ': hasPTZ,
'url': url,
'matrixType': matrixType.index,
};
}

Expand All @@ -280,6 +290,7 @@ class Device {
Server.fromJson(json['server'] as Map<String, dynamic>),
hasPTZ: json['hasPTZ'] ?? false,
url: json['url'],
matrixType: MatrixType.values[json['matrixType'] ?? 0],
);
}
}
21 changes: 12 additions & 9 deletions lib/providers/desktop_view_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,27 +99,28 @@ class DesktopViewProvider extends ChangeNotifier {
}

/// Adds [device] to the current layout
Future<void> add(Device device) {
Future<void> add(Device device, [Layout? layout]) {
assert(
!currentLayout.devices.contains(device),
'The device is already in the layout',
);

assert(device.status, 'The device must be online');

if (!currentLayout.devices.contains(device)) {
layout ??= currentLayout;

if (!layout.devices.contains(device)) {
// If it's a single view layout, ensure the player will be disposed
// properly before adding one
if (currentLayout.type == DesktopLayoutType.singleView) {
var previousDevice = currentLayout.devices.firstOrNull;
if (layout.type == DesktopLayoutType.singleView) {
var previousDevice = layout.devices.firstOrNull;
if (previousDevice != null) {
currentLayout.devices.clear();
layout.devices.clear();
_releaseDevice(device);
}
}

UnityPlayers.players[device.uuid] ??= UnityPlayers.forDevice(device);
currentLayout.devices.add(device);
layout.devices.add(device);
debugPrint('Added $device');

notifyListeners();
Expand Down Expand Up @@ -178,15 +179,17 @@ class DesktopViewProvider extends ChangeNotifier {
}

/// Adds a new layout
Future<void> addLayout(Layout layout) {
Future<int> addLayout(Layout layout) async {
if (!layouts.contains(layout)) {
debugPrint('Added $layout');
layouts.add(layout);
} else {
debugPrint('$layout already exists');
}
notifyListeners();
return _save(notifyListeners: false);
await _save(notifyListeners: false);

return layouts.indexOf(layout);
}

/// Deletes [layout]
Expand Down
93 changes: 93 additions & 0 deletions lib/utils/app_links.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* This file is a part of Bluecherry Client (https://github.com/bluecherrydvr/unity).
*
* Copyright 2022 Bluecherry, LLC
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'dart:io';

import 'package:app_links/app_links.dart';
import 'package:bluecherry_client/main.dart';
import 'package:bluecherry_client/utils/methods.dart';
import 'package:bluecherry_client/widgets/device_grid/desktop/external_stream.dart';
import 'package:flutter/widgets.dart';
import 'package:win32_registry/win32_registry.dart';

final instance = AppLinks();

/// Registers a scheme the app will listen.
Future<void> register(String scheme) async {
if (Platform.isWindows) {
var appPath = Platform.resolvedExecutable;

var protocolRegKey = 'Software\\Classes\\$scheme';
var protocolRegValue = const RegistryValue(
'URL Protocol',
RegistryValueType.string,
'',
);
var protocolCmdRegKey = 'shell\\open\\command';
var protocolCmdRegValue = RegistryValue(
'',
RegistryValueType.string,
'"$appPath" "%1"',
);

Registry.currentUser.createKey(protocolRegKey)
..createValue(protocolRegValue)
..createKey(protocolCmdRegKey).createValue(protocolCmdRegValue);
}
}

/// Initialize the app links.
///
/// When the app is opened from a link, it will open the [AddExternalStreamDialog]
/// as soon as the application is ready.
Future<void> init() async {
try {
var initialUri = await instance.getInitialAppLinkString();
debugPrint('Initial URI: $initialUri');
if (initialUri != null) {
final url = initialUri;
WidgetsBinding.instance.addPostFrameCallback((_) {
final context = navigatorKey.currentContext;
if (context != null && context.mounted) {
AddExternalStreamDialog.addStream(context, url);
}
});
}
} catch (e) {
debugPrint('Error initializing app links: $e');
}
}

/// Listens to any links received while the app is running.
void listen() {
instance.allUriLinkStream.listen((uri) {
debugPrint('Received URI: $uri');
final url = uri.toString();
if (isDesktopPlatform) {
final context = navigatorKey.currentContext;
if (context != null) {
AddExternalStreamDialog.addStream(context, url);
}
} else {
final navigator = navigatorKey.currentState;
if (navigator == null) return;
navigator.pushNamed('/rtsp', arguments: url);
}
});
}
2 changes: 1 addition & 1 deletion lib/widgets/device_grid/desktop/desktop_device_grid.dart
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ class _DesktopTileViewportState extends State<DesktopTileViewport> {
final states = HoverButton.of(context).states;

return Stack(children: [
const Positioned.fill(child: MulticastViewport()),
Positioned.fill(child: MulticastViewport(device: widget.device)),
if (error != null)
Positioned.fill(child: ErrorWarning(message: error)),
IgnorePointer(
Expand Down

0 comments on commit fdb2827

Please sign in to comment.