Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e530f81
Remove dont support audio prompt
aaravlu Feb 17, 2025
3c26329
Remove TODO prompt & add basic
aaravlu Feb 17, 2025
6f1704a
Add svg file 'download'
aaravlu Feb 17, 2025
7fffb21
Add audio player base
aaravlu Feb 18, 2025
7402dd4
Change message in timeline
aaravlu Feb 18, 2025
acc3c36
Finished Audio view base
aaravlu Feb 18, 2025
9e632c0
audio playing issues
aaravlu Feb 19, 2025
38c6cec
Every audio player possess a sink
aaravlu Feb 21, 2025
d973965
Finished pause functionality
aaravlu Feb 21, 2025
ab559ed
Merge branch 'main' into fix120
aaravlu Feb 21, 2025
39b1fd9
Resolve conflicts
aaravlu Feb 21, 2025
1bc325e
Merge branch 'main' into fix120
aaravlu Feb 28, 2025
7540d59
Change audio button color
aaravlu Mar 3, 2025
d63ac99
Replace field 'inisialized' with func 'after_new_from_doc'
aaravlu Mar 3, 2025
e5fa09a
Optimize button UI
aaravlu Mar 3, 2025
cfc158c
Merge branch 'main' into fix120
aaravlu Mar 5, 2025
07c5199
Update 'Cargo.lock'
aaravlu Mar 5, 2025
9364026
Merge branch 'main' into fix120
aaravlu Mar 30, 2025
47a92aa
Audio player base
aaravlu Apr 7, 2025
550b6c9
Audio Player base
aaravlu Apr 7, 2025
fc6a0ba
Play Noise
aaravlu Apr 7, 2025
0504c86
All audios use one sink
aaravlu Apr 8, 2025
a290732
Merge branch 'main' into fix120
aaravlu Apr 9, 2025
e381073
Complish audio player base
aaravlu Apr 9, 2025
0200e67
Remove '.log' file
aaravlu Apr 9, 2025
fe3062a
Fix media cache fecthing failed
aaravlu Apr 9, 2025
ea1b4d4
Add fetching prompt for audio message
aaravlu Apr 9, 2025
e3b6a8a
Refactor audio module
aaravlu Apr 9, 2025
48dd0a6
Correct audio position
aaravlu Apr 9, 2025
82744ab
Remove redadant svg files
aaravlu Apr 9, 2025
d4f4565
Update src/home/room_screen.rs
aaravlu Apr 9, 2025
c5f7f96
Update src/audio/audio_controller.rs
aaravlu Apr 9, 2025
f1177b6
Fix copilot review errors
aaravlu Apr 9, 2025
f57eac1
revert 'Cargo.lock' to main
aaravlu Apr 9, 2025
5c679e5
Remove play & pause icon const
aaravlu Apr 9, 2025
979f36a
Fix doc
aaravlu Apr 10, 2025
dd1c3ba
Restore audio message ui status when being dropped
aaravlu Apr 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions resources/icons/download.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions resources/icons/pause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions resources/icons/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ live_design! {
use crate::shared::popup_list::PopupList;
use crate::home::new_message_context_menu::*;
use crate::shared::callout_tooltip::CalloutTooltip;
use crate::audio::audio_controller::AudioController;


APP_TAB_COLOR = #344054
Expand Down Expand Up @@ -126,6 +127,7 @@ live_design! {
<PopupList> {}
}
}
audio_controller = <AudioController> {}

// Context menus should be shown above other UI elements,
// but beneath the verification modal.
Expand Down Expand Up @@ -174,6 +176,7 @@ impl LiveRegister for App {
crate::home::live_design(cx);
crate::profile::live_design(cx);
crate::login::live_design(cx);
crate::audio::live_design(cx);
}
}

Expand Down
290 changes: 290 additions & 0 deletions src/audio/audio_controller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
//! Audio controller, which just manages audio playback.
//! In UI interaction, it is just a template.

use std::{collections::HashMap, sync::{Arc, LazyLock, Mutex, RwLock}};
use makepad_widgets::*;

use super::audio_message_ui::AudioMessageUIAction;

live_design! {
use link::theme::*;
use link::shaders::*;
use link::widgets::*;

use crate::shared::styles::*;
use crate::shared::icon_button::*;

// width & height is 0 because it is just a template.
pub AudioController = {{AudioController}} {
width: 0., height: 0.,
visible: false,
}
}

#[derive(Debug, Clone, DefaultNone)]
pub enum AudioControllerAction {
UiToPause(WidgetUid),
None,
}

#[derive(Debug, Clone, Default)]
pub struct Audio {
pub data: Arc<[u8]>,
pub channels: u16,
pub bit_depth: u16
}

type Audios = HashMap<WidgetUid, (Audio, Arc<Mutex<usize>>)>;

pub static AUDIO_SET: LazyLock<RwLock<Audios>> = LazyLock::new(||{
RwLock::new(HashMap::new())
});

#[derive(Debug, Clone, Default)]
pub enum Selected {
Playing(WidgetUid, usize),
Paused(WidgetUid, usize),
#[default]
None,
}

#[derive(Live, LiveHook, Widget)]
pub struct AudioController {
#[deref] view: View,
#[rust] audio: Arc<Mutex<Audio>>,
#[rust] selected: Arc<Mutex<Selected>>,
}

impl Drop for AudioController {
fn drop(&mut self) {
// todo!()
}
}

impl Widget for AudioController {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.match_event(cx, event);
self.view.handle_event(cx, event, scope);
}

fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
self.view.draw_walk(cx, scope, walk)
}
}

impl MatchEvent for AudioController {
fn handle_startup(&mut self, cx: &mut Cx) {
let audio = self.audio.clone();
let selected = self.selected.clone();
cx.audio_output(0, move |_audio_info, output_buffer|{
let mut selected_mg = selected.lock().unwrap();
let audio_mg = audio.lock().unwrap();
if let Selected::Playing(uid, mut pos) = selected_mg.clone() {
let audio = audio_mg.clone();
match (audio.channels, audio.bit_depth) {
(2, 16) => {
// stereo 16bit
output_buffer.zero();
let (left, right) = output_buffer.stereo_mut();
let mut i = 0;
while i < left.len() {
let left_i16 = i16::from_le_bytes([audio.data[pos], audio.data[pos + 1]]);
let right_i16 = i16::from_le_bytes([audio.data[pos + 2], audio.data[pos + 3]]);

left[i] = left_i16 as f32 / i16::MAX as f32;
right[i] = right_i16 as f32 / i16::MAX as f32;
pos += 4;
i += 1;
*selected_mg = Selected::Playing(uid, pos + 4);
}
}
(2, 24) => {
// stereo 24bit
output_buffer.zero();
let (left, right) = output_buffer.stereo_mut();
let mut i = 0;
while i < left.len() {
let left_i32 = i32::from_le_bytes([0, audio.data[pos], audio.data[pos + 1], audio.data[pos + 2]]);
let right_i32 = i32::from_le_bytes([0, audio.data[pos + 3], audio.data[pos + 4], audio.data[pos + 5]]);

left[i] = left_i32 as f32 / i32::MAX as f32;
right[i] = right_i32 as f32 / i32::MAX as f32;
pos += 6;
i += 1;
*selected_mg = Selected::Playing(uid, pos + 6);
}
}
(2, 32) => {
// stereo 24bit
output_buffer.zero();
let (left, right) = output_buffer.stereo_mut();
let mut i = 0;
while i < left.len() {
let left_i32 = i32::from_le_bytes([audio.data[pos], audio.data[pos + 1], audio.data[pos + 2], audio.data[pos + 3]]);
let right_i32 = i32::from_le_bytes([audio.data[pos + 4], audio.data[pos + 5], audio.data[pos + 6], audio.data[pos + 7]]);

left[i] = left_i32 as f32 / i32::MAX as f32;
right[i] = right_i32 as f32 / i32::MAX as f32;
pos += 8;
i += 1;
*selected_mg = Selected::Playing(uid, pos + 8);
}
}
_ => { }
}
if pos > audio.data.len() {
*selected_mg = Selected::None;
}
} else {
output_buffer.zero();
}
});
}

fn handle_audio_devices(&mut self, cx: &mut Cx, e: &AudioDevicesEvent) {
cx.use_audio_outputs(&e.default_output())
}
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
for action in actions {
match action.downcast_ref() {
Some(AudioMessageUIAction::Play(new_uid)) => {
let selected =self.selected.clone().lock().unwrap().clone();
match selected {
Selected::Playing(current_uid, current_pos) => {
if &current_uid != new_uid {
cx.action(AudioControllerAction::UiToPause(current_uid));
if let Some((_old_uid, old_pos)) = AUDIO_SET.write().unwrap().get(&current_uid) {
*old_pos.lock().unwrap() = current_pos;
}
if let Some((audio, new_pos)) = AUDIO_SET.read().unwrap().get(new_uid) {
*self.audio.lock().unwrap() = audio.clone();
let new_pos = *new_pos.lock().unwrap();
*self.selected.lock().unwrap() = Selected::Playing(*new_uid, new_pos);
}
}
}
Selected::Paused(current_uid, current_pos) => {
if &current_uid == new_uid {
*self.selected.lock().unwrap() = Selected::Playing(*new_uid, current_pos);
} else {
if let Some((_, old_pos)) = AUDIO_SET.write().unwrap().get_mut(&current_uid) {
*old_pos.lock().unwrap() = current_pos;
}

if let Some((audio, new_pos)) = AUDIO_SET.read().unwrap().get(new_uid) {
*self.audio.lock().unwrap() = audio.clone();
let new_pos = *new_pos.lock().unwrap();
*self.selected.lock().unwrap() = Selected::Playing(*new_uid, new_pos);
}
}
}
Selected::None => {
if let Some((audio, _old_pos)) = AUDIO_SET.write().unwrap().get_mut(new_uid) {
*self.selected.lock().unwrap() = Selected::Playing(*new_uid, 44);
*self.audio.lock().unwrap() = audio.clone();
}
}
}
}
Some(AudioMessageUIAction::Pause(new_uid)) => {
let selected =self.selected.clone().lock().unwrap().clone();
if let Selected::Playing(current_uid, current_pos) = selected {
if &current_uid == new_uid {
if let Some((_, old_pos)) = AUDIO_SET.write().unwrap().get(&current_uid) {
*old_pos.lock().unwrap() = current_pos;
}
*self.selected.lock().unwrap() = Selected::Paused(*new_uid, current_pos);
}
}
}
Some(AudioMessageUIAction::Stop(new_uid)) => {
let selected =self.selected.clone().lock().unwrap().clone();
match selected {
Selected::Playing(current_uid, _current_pos) => {
if &current_uid == new_uid {
if let Some((_, old_pos)) = AUDIO_SET.write().unwrap().get(&current_uid) {
*old_pos.lock().unwrap() = 44;
}
*self.selected.lock().unwrap() = Selected::None;
}
}
Selected::Paused(current_uid, _current_pos) => {
if &current_uid == new_uid {
if let Some((_, old_pos)) = AUDIO_SET.write().unwrap().get(&current_uid) {
*old_pos.lock().unwrap() = 44;
}
}
*self.selected.lock().unwrap() = Selected::None;
}
_ => { }
}
}
_ => { }
}
}
}
}

pub fn insert_new_audio(audio_control_interface_uid: WidgetUid, data: Arc<[u8]>, channels: &u16, bit_depth: &u16) {
let audio = Audio {
data,
channels: *channels,
bit_depth: *bit_depth
};
let pos = Arc::new(Mutex::new(44));
AUDIO_SET.write().unwrap().insert(audio_control_interface_uid, (audio, pos));
}


pub fn parse_wav(data: &[u8]) -> Option<(u16, u16)> {
// Check that the data length is sufficient, at least 44 bytes are required (standard WAV file header size)
if data.len() < 44 {
log!("Insufficient data length");
return None;
}

// Check if the first 4 bytes are 'RIFF'.
if &data[0..4] != b"RIFF" {
log!("Not a `RIFF` file");
return None;
}

// Check if bytes 8-11 are 'WAVE'.
if &data[8..12] != b"WAVE" {
log!("Not a `WAVE` file");
return None;
}

// Check if bytes 12-15 are 'fmt'.
if &data[12..16] != b"fmt " {
log!("`fmt` block not found");
return None;
}

// Read the size of the fmt block (bytes 16-19)
let fmt_size = u32::from_le_bytes([data[16], data[17], data[18], data[19]]);
if fmt_size < 16 {
log!("`fmt` block size too small");
return None;
}

// Read the audio format (bytes 20-21) and make sure it's PCM (value 1)
let audio_format = u16::from_le_bytes([data[20], data[21]]);
if audio_format != 1 {
log!("Not a `PCM` file");
return None;
}

// Extract the number of channels (bytes 22-23)
let channels = u16::from_le_bytes([data[22], data[23]]);

// Extract sampling rate (bytes 24-27)
// let sample_rate = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);

// Extract bit depth (bytes 34-35)
let bit_depth = u16::from_le_bytes([data[34], data[35]]);

// Return the parsing result
// Some((channels, sample_rate, bits_per_sample))
Some((channels, bit_depth))
}
Loading