7 Commits
mvp5 ... v1

9 changed files with 94 additions and 39 deletions

View File

@@ -11,8 +11,8 @@ or to facilitate user input for internet radio sites.
This app has been designed to run across all platforms! This app has been designed to run across all platforms!
- Android - Android ([Google Play Store](https://play.google.com/store/apps/details?id=net.ything.radio.android))
- iOS - iOS (Requires [TestFlight](https://itunes.apple.com/us/app/testflight/id899247664?mt=8), contact info@ything.app for invitation)
- MacOS - MacOS
- Windows - Windows
- Linux - Linux
@@ -25,6 +25,5 @@ Want to have us modify this app for you? Reach out to info@ything.app
Coming Soon: Coming Soon:
- Apple App Store Live Demo! - Apple App Store Live Demo!
- Google Play Store Live Demo!
- Windows portable exe - Windows portable exe
- Screenshots / Demo Videos - Screenshots / Demo Videos

View File

@@ -31,10 +31,10 @@ android {
applicationId = "net.ything.radio.android" applicationId = "net.ything.radio.android"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = 24
targetSdk = flutter.targetSdkVersion targetSdk = 34
versionCode = flutter.versionCode versionCode = 2
versionName = flutter.versionName versionName = "1.0.2"
} }

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ything_radio/SafeZone.dart'; import 'package:ything_radio/SafeZone.dart';
import 'Globals.dart';
class About extends StatelessWidget { class About extends StatelessWidget {
const About({super.key}); const About({super.key});
@@ -17,31 +19,9 @@ class About extends StatelessWidget {
child: SafeZone( child: SafeZone(
child: DefaultTextStyle( child: DefaultTextStyle(
style: Theme.of(context).textTheme.bodyLarge!, style: Theme.of(context).textTheme.bodyLarge!,
child: const SingleChildScrollView( child: SingleChildScrollView(
child: Text( child: Text(
'Ything Radio is an app that is intended to provide a demonstration of the ' getAboutText(),
'open source ything_radio app. This app is used to provide the ability for the '
'"look and feel" of the app to be tested on real devices for those wishing to '
'either use the code as a template for themselves; or, alternatively, have Ything LLC '
'create a custom app for them!'
'\n\n'
'This app intentionally does not provide a method to change the streaming source '
'as it is intended for use by internet radio stations to provide a dedicated app '
'for their radio station. This is not intended to be an app for generic usage for '
'multiple radio stations, a discovery platform, or to facilitate user input for '
'internet radio sites.'
'\n\n'
'This app has been designed to run across all platforms!'
'\n\n'
'Android, iOS, MacOS, Windows, Linux, and Web!'
'\n\n'
'Have a single unified experience across all platforms for your users!'
'\n\n'
'Want to have us modify this app for you? Reach out at info@ything.net '
'We can add additional features for you! Schedule views, up next listings, '
'and even custom user interactivity! '
'\n\n'
'We look forward to hearing from you soon! We would love to build your app!',
textAlign: TextAlign.justify, textAlign: TextAlign.justify,
), ),
), ),

View File

@@ -1,11 +1,55 @@
import 'dart:io' show Platform;
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart'; import 'package:audio_session/audio_session.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:http/http.dart' as http;
import 'ListenHandler.dart'; import 'ListenHandler.dart';
late final ListenHandler _listenHandler; late final ListenHandler _listenHandler;
late final AudioSession _session; late final AudioSession _session;
const String _fallback =
"https://generic.ything.app/music/separation-185196.mp3";
final _urlSource = Uri.parse("https://generic.ything.app/music/player.url");
late final String _radioUrl;
Future<void> loadCurrentUrl() async {
final resp = await http.get(_urlSource);
if (resp.statusCode == 200) {
_radioUrl = resp.body.trim();
print('Loaded remote url');
} else {
_radioUrl = _fallback;
print('Request for current streaming url failed, using fallback url');
}
}
const String _fallbackAbout =
"Ything Radio is an internet radio streaming application.";
final _urlAbout =
Uri.parse("https://generic.ything.app/ything_radio/about.txt");
final _urlAboutIOS =
Uri.parse("https://generic.ything.app/ything_radio/about_ios.txt");
late final String _remoteAbout;
Future<void> loadAboutUrl() async {
final resp = await http.get(Platform.isIOS ? _urlAboutIOS : _urlAbout);
if (resp.statusCode == 200) {
_remoteAbout = resp.body;
print('Loaded remote about');
} else {
_remoteAbout = _fallbackAbout;
print('Request for remote about failed, using fallback about');
}
}
String getAboutText() => _remoteAbout;
Future<void> setupListenHandler() async { Future<void> setupListenHandler() async {
_listenHandler = await AudioService.init( _listenHandler = await AudioService.init(
builder: () => ListenHandler(), builder: () => ListenHandler(),
@@ -51,6 +95,10 @@ Future<void> setupListenHandler() async {
ListenHandler getListenHandlder() => _listenHandler; ListenHandler getListenHandlder() => _listenHandler;
UrlSource getUrlSource() {
return UrlSource(_radioUrl, mimeType: "audio/mpeg");
}
Future<bool> startAudioSession() async { Future<bool> startAudioSession() async {
return await _session.setActive(true); return await _session.setActive(true);
} }

View File

@@ -36,6 +36,8 @@ class _PlayControlsState extends State<PlayControls>
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
bool _isLoading = false;
final ListenHandler _listenHandler = getListenHandlder(); final ListenHandler _listenHandler = getListenHandlder();
@override @override
@@ -46,7 +48,11 @@ class _PlayControlsState extends State<PlayControls>
_listenHandler.playbackState.listen((PlaybackState state) { _listenHandler.playbackState.listen((PlaybackState state) {
print("Playback state changed"); print("Playback state changed");
setState(() {}); setState(() {
//if state changed, we're no longer loading!
//doesn't matter if we're playing or not!
_isLoading = false;
});
}); });
} }
@@ -76,14 +82,26 @@ class _PlayControlsState extends State<PlayControls>
child: InkWell( child: InkWell(
splashColor: Theme.of(context).colorScheme.onPrimaryContainer, splashColor: Theme.of(context).colorScheme.onPrimaryContainer,
onTap: () async { onTap: () async {
if (_isLoading) {
//skip doing anything else if we're still loading...
return;
}
await playPause(); await playPause();
setState(() {}); setState(() {
if (!_listenHandler.isPlaying()) {
_isLoading = true;
}
});
}, },
child: SizedBox( child: SizedBox(
width: 100, width: 100,
height: 100, height: 100,
child: Icon( child: Icon(
_listenHandler.isPlaying() ? Icons.stop : Icons.play_arrow, _isLoading
? Icons.hourglass_empty
: _listenHandler.isPlaying()
? Icons.stop
: Icons.play_arrow,
color: Colors.white, color: Colors.white,
size: 50, size: 50,
), ),

View File

@@ -5,9 +5,7 @@ import 'package:ything_radio/Globals.dart';
class ListenHandler extends BaseAudioHandler { class ListenHandler extends BaseAudioHandler {
final _player = AudioPlayer(); final _player = AudioPlayer();
final UrlSource _radioSource = UrlSource( final UrlSource _radioSource = getUrlSource();
"https://generic.ything.app/music/separation-185196.mp3",
mimeType: "audio/mpeg");
setup_player() { setup_player() {
_player.setReleaseMode(ReleaseMode.release); _player.setReleaseMode(ReleaseMode.release);
@@ -67,6 +65,8 @@ class ListenHandler extends BaseAudioHandler {
@override @override
Future<void> play() async { Future<void> play() async {
if (await startAudioSession()) { if (await startAudioSession()) {
//setSource followed by resume does not work on Android or web
//play works everywhere though
_player.play(_radioSource, mode: PlayerMode.mediaPlayer); _player.play(_radioSource, mode: PlayerMode.mediaPlayer);
} }
} }
@@ -79,4 +79,11 @@ class ListenHandler extends BaseAudioHandler {
@override @override
Future<void> stop() => pause(); Future<void> stop() => pause();
//make appo stop playing when swiped away in task manager on Android
@override
Future<void> onTaskRemoved() async {
await stop();
return super.onTaskRemoved();
}
} }

View File

@@ -4,6 +4,8 @@ import 'Globals.dart';
import 'YthingRadio.dart'; import 'YthingRadio.dart';
Future<void> main() async { Future<void> main() async {
await loadCurrentUrl();
await loadAboutUrl();
await setupListenHandler(); await setupListenHandler();
runApp(const YthingRadio()); runApp(const YthingRadio());
} }

View File

@@ -273,7 +273,7 @@ packages:
source: hosted source: hosted
version: "7.7.0" version: "7.7.0"
http: http:
dependency: transitive dependency: "direct main"
description: description:
name: http name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.1+a version: 1.0.2
environment: environment:
sdk: ^3.5.1 sdk: ^3.5.1
@@ -40,6 +40,7 @@ dependencies:
flutter_tabler_icons: ^1.38.0 flutter_tabler_icons: ^1.38.0
audio_service: ^0.18.15 audio_service: ^0.18.15
audio_session: ^0.1.21 audio_session: ^0.1.21
http: ^1.2.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: