Skip to content

Commit

Permalink
add simple music support and wavlake support
Browse files Browse the repository at this point in the history
  • Loading branch information
haorendashu committed Apr 4, 2024
1 parent 047dc04 commit 6e3d8ca
Show file tree
Hide file tree
Showing 12 changed files with 533 additions and 1 deletion.
Binary file added assets/imgs/music/wavlake.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions lib/component/content/content_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'package:google_mlkit_language_id/google_mlkit_language_id.dart';
import 'package:google_mlkit_translation/google_mlkit_translation.dart';
import 'package:nostrmo/client/cashu/cashu_tokens.dart';
import 'package:nostrmo/component/content/content_decoder.dart';
import 'package:nostrmo/component/content/content_music_component.dart';
import 'package:nostrmo/component/music/wavlake/wavlake_track_music_info_builder.dart';
import 'package:nostrmo/consts/base_consts.dart';
import 'package:nostrmo/provider/setting_provider.dart';
import 'package:nostrmo/util/string_util.dart';
Expand Down Expand Up @@ -481,6 +483,21 @@ class _ContentComponent extends State<ContentComponent> {
}
return null;
} else if (pathType == "link") {
if (wavlakeTrackMusicInfoBuilder.check(str)) {
// check if it is wavlake track link
String? eventId;
if (widget.event != null) {
eventId = widget.event!.id;
}
bufferToList(buffer, allList, removeLastSpan: true);
var w =
ContentMusicComponent(eventId, str, wavlakeTrackMusicInfoBuilder);
allList.add(WidgetSpan(child: w));
counterAddLines(fake_music_counter);

return null;
}

if (!widget.showLinkPreview) {
// inline
bufferToList(buffer, allList);
Expand All @@ -493,6 +510,7 @@ class _ContentComponent extends State<ContentComponent> {
allList.add(WidgetSpan(child: w));
counterAddLines(fake_link_pre_counter);
}

return null;
}
} else if (str.indexOf(PRE_NOSTR_BASE) == 0 ||
Expand Down Expand Up @@ -850,6 +868,8 @@ class _ContentComponent extends State<ContentComponent> {

int fake_link_pre_counter = 7;

int fake_music_counter = 3;

int fake_zap_counter = 6;

void counterAddLines(int lineNum) {
Expand Down
58 changes: 58 additions & 0 deletions lib/component/content/content_music_component.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:nostrmo/component/cust_state.dart';
import 'package:nostrmo/component/music/music_component.dart';
import 'package:nostrmo/component/music/music_info_builder.dart';
import 'package:nostrmo/consts/base.dart';
import 'package:nostrmo/main.dart';
import 'package:nostrmo/provider/music_provider.dart';
import 'package:nostrmo/util/encrypt_util.dart';
import 'package:nostrmo/util/hash_util.dart';

class ContentMusicComponent extends StatefulWidget {
String? eventId;

String content;

MusicInfoBuilder musicInfoBuilder;

ContentMusicComponent(this.eventId, this.content, this.musicInfoBuilder);

@override
State<StatefulWidget> createState() {
return _ContentMusicComponent();
}
}

class _ContentMusicComponent extends CustState<ContentMusicComponent> {
MusicInfo? musicInfo;

@override
Widget doBuild(BuildContext context) {
if (musicInfo == null) {
return Container();
}

return Container(
margin: const EdgeInsets.only(
top: Base.BASE_PADDING_HALF,
bottom: Base.BASE_PADDING_HALF,
),
child: MusicComponent(
musicInfo!,
key: Key(HashUtil.md5(musicInfo!.sourceUrl!)),
),
);
}

@override
Future<void> onReady(BuildContext context) async {
musicInfo = musicInfoCache.get(widget.content);
musicInfo ??=
await widget.musicInfoBuilder.build(widget.content, widget.eventId);
if (musicInfo != null) {
musicInfoCache.put(widget.content, musicInfo!);
}
setState(() {});
}
}
184 changes: 184 additions & 0 deletions lib/component/music/music_component.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:nostrmo/component/webview_router.dart';
import 'package:nostrmo/consts/base.dart';
import 'package:nostrmo/main.dart';
import 'package:nostrmo/util/duartion_tool.dart';
import 'package:provider/provider.dart';

import '../../provider/music_provider.dart';
import '../image_component.dart';

class MusicComponent extends StatefulWidget {
MusicInfo musicInfo;

bool clearAble;

MusicComponent(this.musicInfo, {super.key, this.clearAble = false});

@override
State<StatefulWidget> createState() {
return _MusicComponent();
}
}

class _MusicComponent extends State<MusicComponent> {
@override
Widget build(BuildContext context) {
var themeData = Theme.of(context);
var cardColor = themeData.cardColor;
var titleFontSize = themeData.textTheme.bodyMedium!.fontSize;
var nameFontSize = themeData.textTheme.bodySmall!.fontSize;
var hintColor = themeData.hintColor;

var subInfoTextStyle = TextStyle(
fontSize: nameFontSize,
fontWeight: FontWeight.w500,
color: hintColor,
);

var _musicProvider = Provider.of<MusicProvider>(context);
var currentDuration = _musicProvider.currentDuration;
var currentPosition = _musicProvider.currentPosition;
bool isCurrent = false;
if (_musicProvider.musicInfo != null &&
_musicProvider.musicInfo!.sourceUrl == widget.musicInfo.sourceUrl) {
isCurrent = true;
}

var imageHeight = (titleFontSize! + nameFontSize!) * 1.7;

var imageWidget = ImageComponent(
imageUrl: widget.musicInfo.imageUrl,
);

var btnIcon = Icons.play_circle_outline;
if (isCurrent && _musicProvider.isPlaying) {
btnIcon = Icons.pause_circle_outline;
}

List<Widget> musicSubInfos = [
Container(
margin: const EdgeInsets.only(right: Base.BASE_PADDING_HALF),
child: Image.asset(
widget.musicInfo.icon,
width: 18,
height: 18,
),
),
Text(
widget.musicInfo.name,
style: subInfoTextStyle,
),
];
if (isCurrent &&
currentDuration != null &&
currentPosition != null &&
currentPosition.inSeconds > 0 &&
currentDuration.inSeconds > 0) {
musicSubInfos.add(Text(
" • ${currentPosition.prettyDuration()} / ${currentDuration.prettyDuration()}",
style: subInfoTextStyle,
));
}

List<Widget> topList = [
Container(
width: imageHeight,
height: imageHeight,
child: imageWidget,
),
Expanded(
child: Container(
padding: const EdgeInsets.only(
left: Base.BASE_PADDING,
right: Base.BASE_PADDING,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
widget.musicInfo.title,
style: TextStyle(
fontSize: titleFontSize,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
children: musicSubInfos,
),
],
),
)),
Container(
width: imageHeight,
height: imageHeight,
child: GestureDetector(
onTap: () {
if (isCurrent) {
musicProvider.playOrPause();
} else {
musicProvider.play(widget.musicInfo);
}
},
child: Icon(btnIcon),
),
),
];
if (widget.clearAble) {
topList.add(Container(
width: imageHeight,
height: imageHeight,
child: GestureDetector(
onTap: () {
musicProvider.stop();
},
child: Icon(Icons.clear),
),
));
}

var topWidget = Row(
children: topList,
);

Widget progressBar = Container();
if (_musicProvider.isPlaying && isCurrent) {
double? value;
if (currentDuration != null &&
currentPosition != null &&
currentPosition.inSeconds > 0 &&
currentDuration.inSeconds > 0) {
value = currentPosition.inSeconds / currentDuration.inSeconds;
}
progressBar = LinearProgressIndicator(
value: value,
);
}

return GestureDetector(
onTap: () {
if (widget.musicInfo.sourceUrl != null &&
widget.musicInfo.sourceUrl!.indexOf("http") == 0) {
WebViewRouter.open(context, widget.musicInfo.sourceUrl!);
}
},
child: Container(
color: cardColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: imageHeight,
child: topWidget,
),
progressBar,
],
),
),
);
}
}
7 changes: 7 additions & 0 deletions lib/component/music/music_info_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:nostrmo/provider/music_provider.dart';

abstract class MusicInfoBuilder {
bool check(String content);

Future<MusicInfo?> build(String content, String? eventId);
}
82 changes: 82 additions & 0 deletions lib/component/music/wavlake/wavlake_track_music_info_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'dart:convert';
import 'dart:developer';

import 'package:nostrmo/component/music/music_info_builder.dart';
import 'package:nostrmo/provider/music_provider.dart';
import 'package:nostrmo/util/dio_util.dart';
import 'package:nostrmo/util/spider_util.dart';
import 'package:nostrmo/util/string_util.dart';

WavlakeTrackMusicInfoBuilder wavlakeTrackMusicInfoBuilder =
WavlakeTrackMusicInfoBuilder();

class WavlakeTrackMusicInfoBuilder extends MusicInfoBuilder {
static const String prefix = "https://wavlake.com/track/";

@override
Future<MusicInfo?> build(String content, String? eventId) async {
// try to fetch info from api
try {
var id = content.replaceAll(prefix, "");
var jsonObj = await DioUtil.get(
"https://catalog-prod-dot-wavlake-alpha.uc.r.appspot.com/v1/episodes/${id}");
if (jsonObj != null && jsonObj["success"] == true) {
var name = jsonObj["data"]["podcast"]["name"];
var title = jsonObj["data"]["title"];

var imageUrl = jsonObj["data"]["podcast"]["artworkUrl"];
var audioUrl = jsonObj["data"]["liveUrl"];

return MusicInfo(
"assets/imgs/music/wavlake.png",
eventId,
title,
name,
audioUrl,
"https://wavlake.com/_next/image?url=${Uri.encodeQueryComponent(imageUrl)}&w=256&q=75",
sourceUrl: content);
}
} catch (e) {}

String? source = await DioUtil.getStr(content);
if (StringUtil.isBlank(source)) {
return null;
}

String nameAndTitleStr =
SpiderUtil.subUntil(source!, "<title>", "</title>");
var strs = nameAndTitleStr.split("•");
if (strs.length < 2) {
return null;
}
var name = strs[0].trim();
var title = strs[1].trim();

String imageUrl =
SpiderUtil.subUntil(source, '<meta property="og:image" content="', '"');
String audioUrl =
SpiderUtil.subUntil(source, '<meta property="og:audio" content="', '"');

if (StringUtil.isBlank(audioUrl) || audioUrl.indexOf("http") != 0) {
return null;
}

return MusicInfo(
"assets/imgs/music/wavlake.png",
eventId,
title,
name,
audioUrl,
"https://wavlake.com/_next/image?url=${Uri.encodeQueryComponent(imageUrl)}&w=256&q=75",
sourceUrl: content);
}

@override
bool check(String content) {
if (content.indexOf(prefix) == 0) {
return true;
}

return false;
}
}
Loading

0 comments on commit 6e3d8ca

Please sign in to comment.