Skip to content

Commit

Permalink
Merge pull request #9 from anandnet/Experimental
Browse files Browse the repository at this point in the history
Experimental
  • Loading branch information
anandnet authored Jul 2, 2023
2 parents c8dd21c + a79fe8b commit dffc927
Show file tree
Hide file tree
Showing 46 changed files with 1,170 additions and 499 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## 1.2.0
* Discover content selecter added in settings
* Equalizer support added
* Lyrics support added
* images resolution changes done
* App new version notifier added
* Hide Search FAB from settings
* Internal client error 403 handled using workaround

## 1.1.0

* Radio feature added
* Search/(Artist-song/videos) list continuation added
* List sorting feature added
* PlayNext option added
* Bug Fixes

## 1.0.1

* Some Network Exceptions handled
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ An app for music streaming made with Flutter(Currently support Android only).
* Streaming quality control
* Skip silence
* Dynamic Theme
* Equalizer support
* Lyrics support
* No Advertisment
* No Login required
* No permission required
Expand All @@ -22,8 +24,9 @@ An app for music streaming made with Flutter(Currently support Android only).

# To Do
* Backup & Restore
* Lyrics support
* Equalizer support

# Troubleshoot
* if you are facing Notification control issue or music playpack stopped by system optimization, please enable ignore battery optimization option from settings

# Disclaimer
```
Expand Down
23 changes: 22 additions & 1 deletion lib/helper.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';


void printERROR(dynamic text, {String tag = "Harmony Music"}) {
debugPrint("\x1B[31m[$tag]: $text");
}
Expand Down Expand Up @@ -77,3 +77,24 @@ void sortArtist(
artistList.sort((a, b) =>
isAscending ? a.name.compareTo(b.name) : b.name.compareTo(a.name));
}

/// Return true if new version available
Future<bool> newVersionCheck(String currentVersion) async {
final tags = (await Dio()
.get("https://api.github.com/repos/anandnet/Harmony-Music/tags"))
.data;
final availableVersion = tags[0]['name'] as String;
List currentVersion_ = currentVersion.substring(1).split(".");
List availableVersion_ = availableVersion.substring(1).split(".");
if (int.parse(availableVersion_[0]) > int.parse(currentVersion_[0])) {
return true;
} else if (int.parse(availableVersion_[1]) > int.parse(currentVersion_[1]) &&
int.parse(availableVersion_[0]) == int.parse(currentVersion_[0])) {
return true;
} else if (int.parse(availableVersion_[2]) > int.parse(currentVersion_[2]) &&
int.parse(availableVersion_[0]) == int.parse(currentVersion_[0]) &&
int.parse(availableVersion_[1]) == int.parse(currentVersion_[1])) {
return true;
}
return false;
}
28 changes: 18 additions & 10 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import 'dart:io';

import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';

import 'package:harmonymusic/services/audio_handler.dart';
import 'package:harmonymusic/services/music_service.dart';
import 'package:harmonymusic/ui/home.dart';
import 'package:harmonymusic/ui/player/player_controller.dart';
import 'package:harmonymusic/ui/screens/settings_screen_controller.dart';
import 'package:harmonymusic/ui/utils/theme_controller.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';

import 'ui/screens/home_screen_controller.dart';
import 'ui/utils/home_library_controller.dart';

Expand Down Expand Up @@ -42,11 +41,18 @@ class MyApp extends StatelessWidget {
});
return GetX<ThemeController>(builder: (controller) {
return GetMaterialApp(
title: 'Harmony Music',
theme: controller.themedata.value,
home: const Home(),
debugShowCheckedModeBanner: false,
);
title: 'Harmony Music',
theme: controller.themedata.value,
home: const Home(),
debugShowCheckedModeBanner: false,
builder: (context, child) {
final scale =
MediaQuery.of(context).textScaleFactor.clamp(1.0, 1.3);
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: scale),
child: child!,
);
});
});
}
}
Expand Down Expand Up @@ -79,7 +85,9 @@ void _setAppInitPrefs() {
"cacheSongs": false,
"skipSilenceEnabled": false,
'streamingQuality': 1,
'themePrimaryColor': 4278199603
'themePrimaryColor': 4278199603,
'discoverContentType': "QP",
'newVersionVisibility': true
});
}
}
12 changes: 1 addition & 11 deletions lib/models/album.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@ class AlbumContent {
AlbumContent({required this.title, required this.albumList});
final String title;
final List<Album> albumList;
factory AlbumContent.fromJson(Map<dynamic, dynamic> json) => AlbumContent(
title: json["title"],
albumList: (json["contents"])
.map<Album?>((item) {
if (item.containsKey('browseId') && !item.containsKey('videoId')) {
return Album.fromJson(item);
}
})
.whereType<Album>()
.toList());
}

class Album {
Expand All @@ -37,7 +27,7 @@ class Album {
artists:json["artists"]!=null? List<Map<dynamic, dynamic>>.from(json["artists"]):[{'name':''}],
year: json['year'],

thumbnailUrl: Thumbnail(json["thumbnails"][0]["url"]).high);
thumbnailUrl: Thumbnail(json["thumbnails"][0]["url"]).medium);

Map<String,dynamic> toJson()=>{
"title":title,
Expand Down
6 changes: 6 additions & 0 deletions lib/models/artist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ class Artist {
]
};
}

class ArtistContent{
ArtistContent(this.content,{this.title = "Artists"});
final List<Artist> content;
final String title;
}
48 changes: 28 additions & 20 deletions lib/models/media_Item_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,60 @@ import 'package:harmonymusic/models/thumbnail.dart';
class MediaItemBuilder {
static MediaItem fromJson(dynamic json, {String? url}) {
String artistName = '';
for (dynamic artist in json['artists']) {
artistName += "${artist['name']} • ";
if (json['artists'] != null) {
for (dynamic artist in json['artists']) {
artistName += "${artist['name']} • ";
}
}

return MediaItem(
id: json["videoId"],
title: json["title"],
duration:json['duration']!=null?Duration(seconds:json['duration']):toDuration(json['length'] ),
album: json['album']!=null ? json['album']['name']:null ,
artist: artistName==""?artistName: artistName.substring(0,artistName.length-2),
artUri: Uri.parse(Thumbnail(json["thumbnails"][0]['url']).high),
duration: json['duration'] != null
? Duration(seconds: json['duration'])
: toDuration(json['length']),
album: json['album'] != null ? json['album']['name'] : null,
artist: artistName == ""
? artistName
: artistName.substring(0, artistName.length - 2),
artUri: Uri.parse(Thumbnail(json["thumbnails"][0]['url']).low),
extras: {
'url': json['url'] ?? url,
'length': json['length'],
'album': json['album'],
'artists': json['artists'],
'date':json['date']
'date': json['date']
});
}

static Duration? toDuration(String? time){

if(time == null){
static Duration? toDuration(String? time) {
if (time == null) {
return null;
}

int sec = 0;
final splitted = time.split(":");
if(splitted.length==3){
sec += int.parse(splitted[0])*3600 + int.parse(splitted[1])*60+int.parse(splitted[2]);
}else if(splitted.length ==2){
sec += int.parse(splitted[0])*60+int.parse(splitted[1]);
}else if(splitted.length ==1){
sec+=int.parse(splitted[0]);
if (splitted.length == 3) {
sec += int.parse(splitted[0]) * 3600 +
int.parse(splitted[1]) * 60 +
int.parse(splitted[2]);
} else if (splitted.length == 2) {
sec += int.parse(splitted[0]) * 60 + int.parse(splitted[1]);
} else if (splitted.length == 1) {
sec += int.parse(splitted[0]);
}
return Duration(seconds: sec);
}
}

static Map<String, dynamic> toJson(MediaItem mediaItem) => {
"videoId": mediaItem.id,
"title": mediaItem.title,
'album': mediaItem.extras!['album'],
'artists': mediaItem.extras!['artists'],
'length': mediaItem.extras!['length'],
'duration': mediaItem.duration!=null? mediaItem.duration!.inSeconds:null,
'date':mediaItem.extras!['date'],
'duration':
mediaItem.duration != null ? mediaItem.duration!.inSeconds : null,
'date': mediaItem.extras!['date'],
'thumbnails': [
{'url': mediaItem.artUri.toString()}
],
Expand Down
14 changes: 1 addition & 13 deletions lib/models/playlist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,6 @@ class PlaylistContent {
PlaylistContent({required this.title, required this.playlistList});
final String title;
final List<Playlist> playlistList;
factory PlaylistContent.fromJson(Map<dynamic, dynamic> json) =>
PlaylistContent(
title: json["title"],
playlistList: (json["contents"])
.map<Playlist?>((item) {
if (item.containsKey('playlistId') &&
!item.containsKey('videoId')) {
return Playlist.fromJson(item);
}
})
.whereType<Playlist>()
.toList());
}

class Playlist {
Expand All @@ -36,7 +24,7 @@ class Playlist {
factory Playlist.fromJson(Map<dynamic, dynamic> json) => Playlist(
title: json["title"],
playlistId: json["playlistId"] ?? json["browseId"],
thumbnailUrl: Thumbnail(json["thumbnails"][0]["url"]).high,
thumbnailUrl: Thumbnail(json["thumbnails"][0]["url"]).medium,
description: json["description"],
songCount: json['itemCount'],
isCloudPlaylist: json["isCloudPlaylist"] ?? true);
Expand Down
11 changes: 4 additions & 7 deletions lib/models/quick_picks.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import 'package:audio_service/audio_service.dart';
import 'package:harmonymusic/models/media_Item_builder.dart';


class QuickPicks{
QuickPicks(this.songList);
class QuickPicks {
QuickPicks(this.songList, {this.title = "Discover"});
List<MediaItem> songList;

factory QuickPicks.fromJson(Map<dynamic,dynamic> json)=>QuickPicks((json['contents']).map<MediaItem>((item)=>MediaItemBuilder.fromJson(item)).toList());
}
final String title;
}
6 changes: 3 additions & 3 deletions lib/models/thumbnail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class Thumbnail {
final String _url;
String sizewith(int size) => "${_url.split("=")[0]}=w$size-h$size-l90-rj";
String get url => _url;
String get high => sizewith(544);
String get medium => sizewith(300);
String get low => sizewith(200);
String get high => sizewith(450);
String get medium => sizewith(350);
String get low => sizewith(150);
}
41 changes: 27 additions & 14 deletions lib/services/audio_handler.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import 'dart:io';

import 'package:audio_service/audio_service.dart';
import 'package:hive/hive.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:audio_service/audio_service.dart';
import 'package:device_equalizer/device_equalizer.dart';

import 'package:harmonymusic/helper.dart';
import 'package:harmonymusic/models/media_Item_builder.dart';
import 'package:harmonymusic/services/utils.dart';
import 'package:harmonymusic/ui/screens/settings_screen_controller.dart';
import 'package:hive/hive.dart';

import 'package:just_audio/just_audio.dart';

import 'package:path_provider/path_provider.dart';

import '../ui/utils/home_library_controller.dart';
import 'music_service.dart';

Expand Down Expand Up @@ -116,6 +114,7 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
final box = Hive.box("songsUrlCache");
if (box.containsKey(mediaItem.value!.id)) {
if (isExpired(url: box.get(mediaItem.value!.id)[1])) {
await _player.stop();
await customAction("playByIndex", {'index': currentIndex});
return;
}
Expand All @@ -127,8 +126,17 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
await _player.seek(curPos, index: 0);
await _player.play();
}
await _player.stop();
networkErrorPause = true;

//Workaround when 403 error encountered
customAction("playByIndex", {'index': currentIndex, 'newUrl': true})
.whenComplete(() async {
await _player.stop();
if (currentSongUrl == null) {
networkErrorPause = true;
} else {
_player.play();
}
});
}
});
}
Expand Down Expand Up @@ -308,9 +316,11 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
} else if (name == 'playByIndex') {
await _playList.clear();
currentIndex = extras!['index'];
final isNewUrlReq = extras['newUrl'] ?? false;
final currentSong = queue.value[currentIndex];
mediaItem.add(currentSong);
final url = await checkNGetUrl(currentSong.id);
final url =
await checkNGetUrl(currentSong.id, generateNewUrl: isNewUrlReq);
currentSongUrl = url;
if (url == null) {
return;
Expand Down Expand Up @@ -387,8 +397,10 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
} else if (name == 'addPlayNextItem') {
final song = extras!['mediaItem'] as MediaItem;
final currentQueue = queue.value;
currentQueue.insert(currentIndex+1, song);
currentQueue.insert(currentIndex + 1, song);
queue.add(currentQueue);
}else if (name == 'openEqualizer') {
await DeviceEqualizer().open(_player.androidAudioSessionId!);
}
}

Expand All @@ -407,7 +419,8 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {

// Work around used [useNewInstanceOfExplode = false] to Fix Connection closed before full header was received issue
Future<String?> checkNGetUrl(String songId,
{bool useNewInstanceOfExplode = false}) async {
{bool useNewInstanceOfExplode = false,
bool generateNewUrl = false}) async {
final songsCacheBox = Hive.box("SongsCache");
if (songsCacheBox.containsKey(songId)) {
printINFO("Got Song from cachedbox ($songId)");
Expand All @@ -420,7 +433,7 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
final newMusicServicesIns =
useNewInstanceOfExplode ? MusicServices(false) : null;
dynamic url;
if (songsUrlCacheBox.containsKey(songId)) {
if (songsUrlCacheBox.containsKey(songId) && !generateNewUrl) {
if (isExpired(url: songsUrlCacheBox.get(songId)[qualityIndex])) {
url = useNewInstanceOfExplode
? await newMusicServicesIns!.getSongUri(songId)
Expand Down
Loading

0 comments on commit dffc927

Please sign in to comment.