Skip to content

Commit

Permalink
working local app link rtsp
Browse files Browse the repository at this point in the history
  • Loading branch information
ghassenbenzahra123 committed Oct 29, 2024
1 parent 7b8e9a1 commit 88d31b3
Show file tree
Hide file tree
Showing 10 changed files with 471 additions and 15 deletions.
13 changes: 12 additions & 1 deletion lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -352,5 +352,16 @@
}
}
},
"surahSelector":"Select Surah"
"surahSelector":"Select Surah",
"save":"Save",
"enterRtspUrl":"Enter RTSP URL",
"addRtspUrl":"Add your RTSP camera stream URL below",
"enableRtspCamera":"Enable RTSP Camera",
"rtspCameraSettings":"RTSP Camera Settings",
"invalidRtspUrl":"Invalid RTSP URL. Please check the URL and try again.",
"validRtspUrl":"RTSP URL validated and saved successfully.",
"rtspCameraSettingTitle":"Live camera connection",
"rtspCameraSettingDesc":"Connect to your local camera and display jumua prayer stream on the TV screen."


}
11 changes: 10 additions & 1 deletion lib/l10n/intl_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,15 @@
"example": "604"
}
}
}
},
"save": "Enregistrer",
"enterRtspUrl": "Entrez l'URL RTSP",
"addRtspUrl": "Ajoutez l'URL du flux de votre caméra RTSP ci-dessous",
"enableRtspCamera": "Activer la caméra RTSP",
"rtspCameraSettings": "Paramètres de la caméra RTSP",
"invalidRtspUrl": "URL RTSP invalide. Veuillez vérifier l'URL et réessayer.",
"validRtspUrl": "URL RTSP validée et enregistrée avec succès.",
"rtspCameraSettingTitle": "Connexion à la caméra en direct",
"rtspCameraSettingDesc": "Connectez-vous à votre caméra locale et affichez le flux de la prière du jumua sur l'écran TV."

}
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'package:mawaqit/src/services/mosque_manager.dart';
import 'package:mawaqit/src/services/settings_manager.dart';
import 'package:mawaqit/src/services/theme_manager.dart';
import 'package:mawaqit/src/services/user_preferences_manager.dart';
import 'package:media_kit/media_kit.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:sizer/sizer.dart';
Expand All @@ -46,6 +47,7 @@ Future<void> main() async {
Hive.registerAdapter(SurahModelAdapter());
Hive.registerAdapter(ReciterModelAdapter());
Hive.registerAdapter(MoshafModelAdapter());
MediaKit.ensureInitialized();
runApp(
ProviderScope(
child: MyApp(),
Expand Down
6 changes: 6 additions & 0 deletions lib/src/const/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,9 @@ abstract class SystemFeaturesConstant {
static const String kHdmi = 'android.hardware.hdmi';
static const String kEthernet = 'android.hardware.ethernet';
}

abstract class RtspCameraStreamConstant {
static const String kRtspUrlKey = 'rtsp_url';
static const String kRtspEnabledKey = 'rtsp_enabled';
static const int kMaxRetries = 3;
}
210 changes: 210 additions & 0 deletions lib/src/pages/RTSPCameraSettingsScreen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mawaqit/i18n/l10n.dart';
import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_notifier.dart';
import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_state.dart';
import 'package:mawaqit/src/widgets/ScreenWithAnimation.dart';

class RTSPCameraSettingsScreen extends ConsumerStatefulWidget {
const RTSPCameraSettingsScreen({Key? key}) : super(key: key);

@override
ConsumerState<RTSPCameraSettingsScreen> createState() => _RTSPCameraSettingsScreenState();
}

class _RTSPCameraSettingsScreenState extends ConsumerState<RTSPCameraSettingsScreen> {
final TextEditingController _urlController = TextEditingController();
bool _isValidating = false;

@override
void initState() {
super.initState();
_loadSettings();
}

void _loadSettings() {
final streamState = ref.read(rtspCameraStreamProvider);
streamState.whenData((state) {
_urlController.text = state.rtspUrl ?? '';
});
}

Future<bool> _validateRtspUrl(String url) async {
if (!url.toLowerCase().startsWith('rtsp://')) {
return false;
}

setState(() => _isValidating = true);

try {
await ref.read(rtspCameraStreamProvider.notifier).updateStream(isEnabled: true, url: url);

await Future.delayed(const Duration(seconds: 2));

final streamState = ref.read(rtspCameraStreamProvider);
return streamState.whenOrNull(
data: (state) => !state.invalidRTSPUrl,
) ??
false;
} catch (e) {
log('Rtsp stream validation error: $e');
return false;
} finally {
if (mounted) {
setState(() => _isValidating = false);
}
}
}

Future<void> _saveSettings() async {
if (_urlController.text.isNotEmpty) {
final isValid = await _validateRtspUrl(_urlController.text);

if (isValid) {
await ref.read(rtspCameraStreamProvider.notifier).updateStream(isEnabled: true, url: _urlController.text);

if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(S.of(context).validRtspUrl),
backgroundColor: Colors.green,
),
);
}
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(S.of(context).invalidRtspUrl),
backgroundColor: Colors.red,
),
);
}
}
}
}

Future<void> _handleEnableToggle(bool value) async {
if (value && _urlController.text.isNotEmpty) {
final isValid = await _validateRtspUrl(_urlController.text);
if (!isValid) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(S.of(context).invalidRtspUrl),
backgroundColor: Colors.red,
),
);
}
return;
}
}

await ref.read(rtspCameraStreamProvider.notifier).updateStream(isEnabled: value, url: _urlController.text);
}

@override
void dispose() {
_urlController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
final streamState = ref.watch(rtspCameraStreamProvider);

return streamState.when(
data: (state) => _buildScreen(state.isRTSPEnabled),
loading: () => const Center(child: CircularProgressIndicator()),
error: (_, __) => _buildScreen(false),
);
}

Widget _buildScreen(bool isEnabled) {
return ScreenWithAnimationWidget(
animation: 'settings',
child: Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
S.of(context).rtspCameraSettings,
style: Theme.of(context).textTheme.titleMedium?.apply(fontSizeFactor: 2),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
SwitchListTile(
title: Text(S.of(context).enableRtspCamera),
value: isEnabled,
onChanged: _handleEnableToggle,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(color: Theme.of(context).dividerColor),
),
),
if (isEnabled) ...[
const SizedBox(height: 20),
Text(
S.of(context).addRtspUrl,
style: Theme.of(context).textTheme.bodyLarge?.apply(fontSizeFactor: 1.2),
textAlign: TextAlign.center,
),
const Divider(indent: 50, endIndent: 50),
const SizedBox(height: 20),
TextField(
controller: _urlController,
decoration: InputDecoration(
labelText: S.of(context).enterRtspUrl,
hintText: 'rtsp://username:password@ip:port/stream',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
),
autofocus: false,
),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: _isValidating
? null
: () {
if (_urlController.text.isNotEmpty) {
_saveSettings();
}
},
icon: _isValidating
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Icon(Icons.save),
label: Text(S.of(context).save),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
),
],
],
),
),
),
],
),
),
),
);
}
}
7 changes: 7 additions & 0 deletions lib/src/pages/SettingScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:mawaqit/src/pages/LanguageScreen.dart';
import 'package:mawaqit/src/pages/MosqueSearchScreen.dart';
import 'package:mawaqit/src/pages/TimezoneScreen.dart';
import 'package:mawaqit/src/pages/WifiSelectorScreen.dart';
import 'package:mawaqit/src/pages/RTSPCameraSettingsScreen.dart';
import 'package:mawaqit/src/pages/onBoarding/widgets/OrientationWidget.dart';
import 'package:mawaqit/src/services/mosque_manager.dart';
import 'package:mawaqit/src/services/theme_manager.dart';
Expand Down Expand Up @@ -176,6 +177,12 @@ class _SettingScreenState extends ConsumerState<SettingScreen> {
);
},
),
_SettingItem(
title: S.of(context).rtspCameraSettingTitle,
subtitle: S.of(context).rtspCameraSettingDesc,
icon: Icon(Icons.video_camera_back, size: 35),
onTap: () => AppRouter.push(RTSPCameraSettingsScreen()),
),
SizedBox(height: 30),
Divider(),
SizedBox(height: 10),
Expand Down
51 changes: 39 additions & 12 deletions lib/src/pages/home/sub_screens/JummuaLive.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mawaqit/i18n/l10n.dart';
import 'package:mawaqit/src/helpers/RelativeSizes.dart';
import 'package:mawaqit/src/models/address_model.dart';
import 'package:mawaqit/src/services/mosque_manager.dart';
import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_notifier.dart';
import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_state.dart';
import 'package:mawaqit/src/themes/UIShadows.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../../../../main.dart';
import '../../../helpers/connectivity_provider.dart';
Expand All @@ -28,42 +31,66 @@ class JummuaLive extends ConsumerStatefulWidget {
}

class _JummuaLiveState extends ConsumerState<JummuaLive> {
/// invalid channel id
bool invalidStreamUrl = false;

@override
void initState() {
invalidStreamUrl = context.read<MosqueManager>().mosque?.streamUrl == null;
log('JummuaLive: invalidStreamUrl: $invalidStreamUrl');
super.initState();
invalidStreamUrl = context.read<MosqueManager>().mosque?.streamUrl == null;
}

@override
Widget build(BuildContext context) {
final mosqueManager = context.read<MosqueManager>();
final userPrefs = context.watch<UserPreferencesManager>();
final connectivity = ref.watch(connectivityProvider);
final streamState = ref.watch(rtspCameraStreamProvider);

/// disable live stream in mosque primary screen
final jumuaaDisableInMosque = !userPrefs.isSecondaryScreen && mosqueManager.typeIsMosque;

return switch (connectivity) {
AsyncData(:final value) => switchStreamWidget(value, mosqueManager, jumuaaDisableInMosque),
AsyncData(:final value) => streamState.when(
data: (state) => switchStreamWidget(value, mosqueManager, jumuaaDisableInMosque, state),
loading: () => CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
),
error: (_, __) => switchStreamWidget(value, mosqueManager, jumuaaDisableInMosque, null),
),
_ => CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor), // Green color
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
),
};
}

Widget switchStreamWidget(
ConnectivityStatus connectivityStatus, MosqueManager mosqueManager, bool jumuaaDisableInMosque) {
Widget switchStreamWidget(ConnectivityStatus connectivityStatus, MosqueManager mosqueManager,
bool jumuaaDisableInMosque, RtspCameraStreamState? streamState) {
// Check for RTSP stream first
if (streamState != null &&
streamState.isRTSPEnabled &&
streamState.isRTSPInitialized &&
!streamState.invalidRTSPUrl &&
connectivityStatus != ConnectivityStatus.disconnected) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: AspectRatio(
aspectRatio: 16 / 9,
child: Video(
controller: ref.read(rtspCameraStreamProvider.notifier).videoController,
),
),
),
);
}

// Fall back to YouTube or Hadith screen
if (invalidStreamUrl ||
mosqueManager.mosque?.streamUrl == null ||
jumuaaDisableInMosque ||
connectivityStatus == ConnectivityStatus.disconnected) {
if (mosqueManager.mosqueConfig!.jumuaDhikrReminderEnabled == true)
if (mosqueManager.mosqueConfig!.jumuaDhikrReminderEnabled == true) {
return JumuaHadithSubScreen(onDone: widget.onDone);

}
return Scaffold(backgroundColor: Colors.black);
} else {
return MawaqitYoutubePlayer(
Expand Down
Loading

0 comments on commit 88d31b3

Please sign in to comment.