Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add recording devices and setVolume to audio provider #170

Merged
merged 23 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,16 @@ No config options.
| Variable | Description | Return type | Supported OS |
| ------------------- | ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `defaultPlaybackDevice` | Default audio playback device. | `AudioDevice \| null` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"> |
| `defaultRecordingDevice` | Default audio recording device. | `AudioDevice \| null` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"> |
| `playbackDevices` | All audio playback devices. | `AudioDevice[]` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"> |
| `recordingDevices` | All audio recording devices. | `AudioDevice[]` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"> |
| `allDevices` | All audio devices (both playback and recording). | `AudioDevice[]` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"> |

#### Return types
| Function | Description | Return type | Supported OS |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setVolume` | Sets the volume of an audio device. Changes the volume of the default playback device, unless `SetVolumeOptions.deviceId` is specified. <br><br> **Parameters:**<br> <br> - `volume`: _`number`_ Volume as a % of maximum volume. Returned value is between `0` and `100`. <br> - `options`: _`SetVolumeOptions \| undefined`_ Additional options.<br> | `Promise<void>` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"> |

#### Related types

#### `AudioDevice`

Expand All @@ -95,7 +102,15 @@ No config options.
| `deviceId` | Device ID. | `string` |
| `name` | Friendly display name of device. | `string` |
| `volume` | Volume as a % of maximum volume. Returned value is between `0` and `100`. | `number` |
| `isDefault` | `true` if the device is selected as the default playback device.| `boolean` |
| `type` | Type of the device. | `'playback' \| 'recording'` |
| `isDefaultPlayback` | `true` if the device is selected as the default playback device.| `boolean` |
| `isDefaultRecording` | `true` if the device is selected as the default recording device.| `boolean` |

#### `SetVolumeOptions`

| Variable | Description | Return type |
| ------------------ | ----------------------------- | ----------------------- |
| `deviceId` | Device ID to set the volume of. | `string \| undefined` |

### Battery

Expand Down Expand Up @@ -171,7 +186,7 @@ No config options.
| ------------------- | ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `disks` | Available disks on the system. | `Disk[]` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"><img src="https://github.com/glzr-io/zebar/assets/34844898/005a0760-da9d-460e-b533-9b2aba7f5c03" alt="apple icon" width="24"><img src="https://github.com/glzr-io/zebar/assets/34844898/1c5d91b1-879f-42a6-945e-912a11daebb4" alt="linux icon" width="24"> |

#### Return types
#### Related types

#### `Disk`

Expand Down
19 changes: 18 additions & 1 deletion examples/boilerplate-react-buildless/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
memory: { type: 'memory' },
weather: { type: 'weather' },
media: { type: 'media' },
audio: { type: 'audio' },
});

createRoot(document.getElementById('root')).render(<App />);
Expand All @@ -50,8 +51,24 @@

return (
<div className="app">
{output.audio?.defaultPlaybackDevice && (
<div class="chip">
{output.audio.defaultPlaybackDevice.name}-
{output.audio.defaultPlaybackDevice.volume}
<input
type="range"
min="0"
max="100"
step="2"
value={output.audio.defaultPlaybackDevice.volume}
onChange={e =>
output.audio.setVolume(e.target.valueAsNumber)
}
/>
</div>
)}
<div class="chip">
Media: {output.media?.currentSession?.title} -
Media: {output.media?.currentSession?.title}-
{output.media?.currentSession?.artist}
<button onClick={() => output.media?.togglePlayPause()}>
Expand Down
20 changes: 15 additions & 5 deletions examples/boilerplate-solid-ts/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,22 @@ function App() {

return (
<div class="app">
{output.audio?.defaultPlaybackDevice && (
<div class="chip">
{output.audio.defaultPlaybackDevice.name}-
{output.audio.defaultPlaybackDevice.volume}
<input
type="range"
min="0"
max="100"
step="2"
value={output.audio.defaultPlaybackDevice.volume}
onChange={e => output.audio.setVolume(e.target.valueAsNumber)}
/>
</div>
)}
<div class="chip">
{output.audio?.defaultPlaybackDevice?.name} -
{output.audio?.defaultPlaybackDevice?.volume}
</div>
<div class="chip">
Media: {output.media?.currentSession?.title} -
Media: {output.media?.currentSession?.title}-
{output.media?.currentSession?.artist}
<button onClick={() => output.media?.togglePlayPause()}>⏯</button>
</div>
Expand Down
23 changes: 22 additions & 1 deletion packages/client-api/src/desktop/desktop-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,28 @@ export const desktopCommands = {
setSkipTaskbar,
};

export type ProviderFunction = MediaFunction;
export type ProviderFunction = AudioFunction | MediaFunction;

export interface AudioFunction {
type: 'audio';
function: {
name: 'set_volume';
args: SetVolumeArgs;
};
}

export interface SetVolumeArgs {
volume: number;
deviceId?: string;
}

export interface MediaFunction {
type: 'media';
function: {
name: 'play' | 'pause' | 'toggle_play_pause' | 'next' | 'previous';
args: MediaControlArgs;
};
}

export interface MediaFunction {
type: 'media';
Expand Down
15 changes: 13 additions & 2 deletions packages/client-api/src/providers/audio/audio-provider-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,24 @@ export interface AudioProviderConfig {
export type AudioProvider = Provider<AudioProviderConfig, AudioOutput>;

export interface AudioOutput {
defaultPlaybackDevice: AudioDevice;
defaultPlaybackDevice: AudioDevice | null;
defaultRecordingDevice: AudioDevice | null;
playbackDevices: AudioDevice[];
recordingDevices: AudioDevice[];
setVolume(volume: number, options?: SetVolumeOptions): Promise<void>;
}

export interface SetVolumeOptions {
deviceId?: string;
}

export interface AudioDevice {
deviceId: string;
name: string;
volume: number;
isDefault: boolean;
type: AudioDeviceType;
isDefaultPlayback: boolean;
isDefaultRecording: boolean;
}

export type AudioDeviceType = 'playback' | 'recording';
31 changes: 23 additions & 8 deletions packages/client-api/src/providers/audio/create-audio-provider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { z } from 'zod';

import { createBaseProvider } from '../create-base-provider';
import { onProviderEmit } from '~/desktop';
import { desktopCommands, onProviderEmit } from '~/desktop';
import type {
AudioOutput,
AudioProvider,
AudioProviderConfig,
SetVolumeOptions,
} from './audio-provider-types';

const audioProviderConfigSchema = z.object({
Expand All @@ -18,12 +19,26 @@ export function createAudioProvider(
const mergedConfig = audioProviderConfigSchema.parse(config);

return createBaseProvider(mergedConfig, async queue => {
return onProviderEmit<AudioOutput>(mergedConfig, ({ result }) => {
if ('error' in result) {
queue.error(result.error);
} else {
queue.output(result.output);
}
});
return onProviderEmit<AudioOutput>(
mergedConfig,
({ configHash, result }) => {
if ('error' in result) {
queue.error(result.error);
} else {
queue.output({
...result.output,
setVolume: (volume: number, options?: SetVolumeOptions) => {
return desktopCommands.callProviderFunction(configHash, {
type: 'audio',
function: {
name: 'set_volume',
args: { volume, deviceId: options?.deviceId },
},
});
},
});
}
},
);
});
}
10 changes: 5 additions & 5 deletions packages/client-api/src/providers/media/create-media-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function createMediaProvider(
...result.output,
session: result.output.currentSession,
play: (args?: MediaControlArgs) => {
desktopCommands.callProviderFunction(configHash, {
return desktopCommands.callProviderFunction(configHash, {
type: 'media',
function: {
name: 'play',
Expand All @@ -37,7 +37,7 @@ export function createMediaProvider(
});
},
pause: (args?: MediaControlArgs) => {
desktopCommands.callProviderFunction(configHash, {
return desktopCommands.callProviderFunction(configHash, {
type: 'media',
function: {
name: 'pause',
Expand All @@ -46,7 +46,7 @@ export function createMediaProvider(
});
},
togglePlayPause: (args?: MediaControlArgs) => {
desktopCommands.callProviderFunction(configHash, {
return desktopCommands.callProviderFunction(configHash, {
type: 'media',
function: {
name: 'toggle_play_pause',
Expand All @@ -55,7 +55,7 @@ export function createMediaProvider(
});
},
next: (args?: MediaControlArgs) => {
desktopCommands.callProviderFunction(configHash, {
return desktopCommands.callProviderFunction(configHash, {
type: 'media',
function: {
name: 'next',
Expand All @@ -64,7 +64,7 @@ export function createMediaProvider(
});
},
previous: (args?: MediaControlArgs) => {
desktopCommands.callProviderFunction(configHash, {
return desktopCommands.callProviderFunction(configHash, {
type: 'media',
function: {
name: 'previous',
Expand Down
35 changes: 35 additions & 0 deletions packages/desktop/src/common/windows/com.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use windows::Win32::System::Com::{
CoInitializeEx, CoUninitialize, COINIT_MULTITHREADED,
};

thread_local! {
/// Manages per-thread COM initialization. COM must be initialized on each
/// thread that uses it, so we store this in thread-local storage to handle
/// the setup and cleanup automatically.
pub static COM_INIT: ComInit = ComInit::new();
}

pub struct ComInit();

impl ComInit {
/// Initializes COM on the current thread with multithreaded object
/// concurrency.
///
/// # Panics
///
/// Panics if COM initialization fails. This is typically only possible
/// if COM is already initialized with an incompatible threading model.
pub fn new() -> Self {
unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }
.ok()
.expect("Unable to initialize COM.");

Self()
}
}

impl Drop for ComInit {
fn drop(&mut self) {
unsafe { CoUninitialize() };
}
}
2 changes: 2 additions & 0 deletions packages/desktop/src/common/windows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod app_bar;
mod com;
mod window_ext_windows;

pub use app_bar::*;
pub use com::*;
pub use window_ext_windows::*;
Loading
Loading