Skip to content

Commit

Permalink
bruig: first version of audio notes
Browse files Browse the repository at this point in the history
  • Loading branch information
miki committed Oct 31, 2024
1 parent 2c91042 commit f577e15
Show file tree
Hide file tree
Showing 36 changed files with 1,883 additions and 101 deletions.
4 changes: 3 additions & 1 deletion bruig/flutterui/bruig/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
namespace 'org.bisonrelay.bruig'
compileSdkVersion 34
ndkVersion flutter.ndkVersion

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand All @@ -36,7 +37,7 @@ android {
defaultConfig {
multiDexEnabled true
applicationId "org.bisonrelay.bruig"
minSdkVersion flutter.minSdkVersion
minSdkVersion 23 // flutter.minSdkVersion
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down Expand Up @@ -72,4 +73,5 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'

api project(":golib")
api project(":opus")
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission-group.MICROPHONE"/>
<application
android:label="bisonrelay"
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/app_icon">
<activity
android:name=".MainActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">127.0.0.1</domain>
</domain-config>
</network-security-config>
2 changes: 2 additions & 0 deletions bruig/flutterui/bruig/android/opus/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
configurations.maybeCreate("default")
artifacts.add("default", file('opus.aar'))
Binary file added bruig/flutterui/bruig/android/opus/opus.aar
Binary file not shown.
1 change: 1 addition & 0 deletions bruig/flutterui/bruig/android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
include ':app'
include ':golib'
include ':opus'

def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
Expand Down
1 change: 1 addition & 0 deletions bruig/flutterui/bruig/lib/components/attach_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ List<String> allowedMimeTypes = [
"image/jxl",
"image/png",
"image/webp",
"audio/ogg",
"application/pdf"
];

Expand Down
199 changes: 199 additions & 0 deletions bruig/flutterui/bruig/lib/components/audio_element.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import 'dart:typed_data';

import 'package:bruig/components/buttons.dart';
import 'package:bruig/components/snackbars.dart';
import 'package:bruig/components/text.dart';
import 'package:bruig/models/audio.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';

class AudioPlayerTracker extends StatefulWidget {
final Uint8List audioBytes;
final AudioModel audio;
const AudioPlayerTracker(
{required this.audioBytes, required this.audio, super.key});

@override
State<AudioPlayerTracker> createState() => _AudioPlayerTrackerState();
}

class _AudioPlayerTrackerState extends State<AudioPlayerTracker> {
AudioModel get audio => widget.audio;
bool get playing => audio.playing && audio.playingSource == widget.audioBytes;
bool listeningPosition = false;

double progress = 0;

void onPositionChanged() {
var length = audio.audioPosition.length.inMilliseconds;
var value = audio.audioPosition.position.inMilliseconds;

// print("YYYYY onPositionChanged tracker $value $length");

if (length == 0) {
return;
}
setState(() {
progress = value / length;
});
}

void update() {
if (!playing) {
if (listeningPosition) {
audio.audioPosition.removeListener(onPositionChanged);
listeningPosition = false;
}
return;
}

if (!listeningPosition) {
audio.audioPosition.addListener(onPositionChanged);
listeningPosition = true;
}

if (audio.playerEvents.lastEvent.processingState ==
ProcessingState.completed) {
if (listeningPosition) {
print("BBBBBBB removing listener");
audio.audioPosition.removeListener(onPositionChanged);
listeningPosition = false;
}
setState(() {
progress = 1;
});
}
}

@override
void initState() {
super.initState();
audio.playerEvents.addListener(update);
update();
}

@override
void didUpdateWidget(covariant AudioPlayerTracker oldWidget) {
super.didUpdateWidget(oldWidget);
update();
}

@override
void dispose() {
audio.playerEvents.removeListener(update);
if (listeningPosition) {
audio.audioPosition.removeListener(onPositionChanged);
}
super.dispose();
}

@override
Widget build(BuildContext context) {
return LinearProgressIndicator(value: progress);
}
}

class AudioElement extends StatefulWidget {
final Uint8List audioBytes;
final String mimeType;
final AudioModel audio;
const AudioElement(
{super.key,
required this.audioBytes,
required this.mimeType,
required this.audio});

@override
State<AudioElement> createState() => _AudioElementState();
}

class _AudioElementState extends State<AudioElement> {
AudioModel get audio => widget.audio;
bool playing = false;
double playSpeed = 1.0;

static const List<double> speeds = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2];

void playStop() async {
if (playing) {
try {
audio.stop();
} catch (exception) {
showErrorSnackbar(this, "Unable to stop audio: $exception");
}
} else {
try {
await audio.player.setSpeed(playSpeed);
await audio.playMemAudio(widget.mimeType, widget.audioBytes);
} catch (exception) {
showErrorSnackbar(this, "Unable to play audio: $exception");
}
}
}

void setPlaySpeed(double? v) {
if (v == null) {
return;
}
setState(() => playSpeed = v);
if (playing) {
audio.player.setSpeed(v);
}
}

void updated() {
var newPlaying = audio.playingSource == widget.audioBytes && audio.playing;
if (playing != newPlaying) {
setState(() {
playing = newPlaying;
});
}
}

@override
void initState() {
super.initState();
audio.addListener(updated);
updated();
}

@override
void didUpdateWidget(covariant AudioElement oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.audio != audio) {
oldWidget.audio.removeListener(updated);
audio.addListener(updated);
updated();
}
}

@override
void dispose() {
audio.removeListener(updated);
super.dispose();
}

@override
Widget build(BuildContext context) {
return (Row(children: [
CircularProgressButton(
active: playing,
inactiveIcon: Icons.play_arrow,
activeIcon: Icons.stop,
onTapDown: playStop),
const SizedBox(width: 20),
Expanded(
child:
AudioPlayerTracker(audio: audio, audioBytes: widget.audioBytes)),
const SizedBox(width: 20),
DropdownButton(
value: playSpeed,
items: speeds
.map((v) =>
DropdownMenuItem<double>(value: v, child: Txt.S("${v}x")))
.toList(),
onChanged: setPlaySpeed,
)
]));
}
}
Loading

0 comments on commit f577e15

Please sign in to comment.