a11y-player
Version:
An accessible DAISY format audiobook player for React applications
415 lines (335 loc) • 11.5 kB
Markdown
# DAISY Player React
A modern, accessible audio player for DAISY (Digital Accessible Information System) format books, built with React and TypeScript.
[](https://www.npmjs.com/package/a11y-player)
[](https://opensource.org/licenses/MIT)
## Installation
```bash
npm install a11y-player
# or
yarn add a11y-player
```
## Quick Start
```jsx
import { DaisyPlayer } from 'a11y-player';
// No need to import CSS separately - styles are included in the bundle
function App() {
return (
<DaisyPlayer
dirUrl="https://example.com/daisy-books/book1"
appUrl="https://myapp.com"
/>
);
}
```
## Features
- **Accessible Audio Playback**: Fully keyboard-navigable player with screen reader support
- **Section Navigation**: Browse and jump to different sections of the audiobook
- **Playback Controls**: Play/pause, skip forward/backward, adjust playback speed
- **Bookmark Sharing**: Generate and share links to specific positions in the audiobook
- **Real-time Callbacks**: Time updates, bookmark changes, and playback state monitoring
- **Programmatic Control**: Full API access via ref interface
- **Event Handling**: Play, pause, and seek event callbacks
- **Responsive Design**: Works on various screen sizes and devices
- **React Router Integration**: Built-in component for URL bookmark parameters
## Components
### DaisyPlayer
The core player component.
#### Props
| Prop | Type | Description | Required |
|------|------|-------------|----------|
| `dirUrl` | string | Base URL for the DAISY book files | Yes |
| `appUrl` | string | Application URL for sharing links | Yes |
| `initialBookmark` | string | Initial bookmark position (format: "smilFile:timeInSeconds") | No |
| `className` | string | Custom class name for the container | No |
| `language` | string | UI language code (default: 'en') | No |
| `pathPrefix` | string | Path prefix for bookmark URLs | No |
#### Callback Props
| Prop | Type | Description | Required |
|------|------|-------------|----------|
| `onTimeUpdate` | `(currentTime: number, bookmark: string, isPlaying: boolean) => void` | Called at regular intervals during playback | No |
| `timeUpdateInterval` | number | Time update interval in milliseconds (default: 1000) | No |
| `onBookmarkChange` | `(bookmark: string) => void` | Called when bookmark position changes | No |
| `onPlaybackStateChange` | `(state: PlaybackState) => void` | Called when playback state changes | No |
| `onPlay` | `() => void` | Called when playback starts | No |
| `onPause` | `() => void` | Called when playback pauses | No |
| `onSeek` | `(bookmark: string) => void` | Called when seeking to a new position | No |
#### PlaybackState Interface
```typescript
interface PlaybackState {
currentTime: number;
duration: number;
isPlaying: boolean;
currentBookmark: string;
playbackRate: number;
}
```
### DaisyPlayerWithRouter
A wrapper around DaisyPlayer that automatically reads the initial bookmark from URL parameters.
#### Props
Inherits all props from `DaisyPlayer` plus:
| Prop | Type | Description | Required |
|------|------|-------------|----------|
| `bookmarkParam` | string | URL parameter name to use for the bookmark (default: "bookmark") | No |
### DaisyPlayerRef Interface
For programmatic control, use a ref to access these methods:
```typescript
interface DaisyPlayerRef {
getCurrentTime(): number;
getDuration(): number;
isPlaying(): boolean;
getCurrentBookmark(): string;
getPlaybackRate(): number;
togglePlayPause(): void;
seek(bookmark: string): void;
}
```
## Usage Examples
### Basic Usage
```jsx
import { DaisyPlayer } from 'a11y-player';
function App() {
return (
<DaisyPlayer
dirUrl="https://example.com/daisy-books/book1"
appUrl="https://myapp.com"
/>
);
}
```
### With React Router Integration
```jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { DaisyPlayerWithRouter } from 'a11y-player';
function App() {
return (
<BrowserRouter>
<Routes>
<Route
path="/:bookmark?"
element={
<DaisyPlayerWithRouter
dirUrl="https://example.com/daisy-books/book1"
appUrl="https://myapp.com"
bookmarkParam="bookmark"
/>
}
/>
</Routes>
</BrowserRouter>
);
}
```
### With Custom Bookmark Handling
```jsx
import { useState } from 'react';
import { DaisyPlayer } from 'a11y-player';
function MyPlayer() {
const [bookmark, setBookmark] = useState(null);
return (
<>
<DaisyPlayer
dirUrl="https://example.com/daisy-books/book1"
appUrl="https://myapp.com"
initialBookmark={bookmark}
onBookmarkChange={(newBookmark) => setBookmark(newBookmark)}
/>
<div>
Current bookmark: {bookmark}
<button onClick={() => localStorage.setItem('savedBookmark', bookmark)}>
Save Position
</button>
<button onClick={() => setBookmark(localStorage.getItem('savedBookmark'))}>
Restore Position
</button>
</div>
</>
);
}
```
### With Time Updates and Auto-Save
```jsx
import { useState, useCallback } from 'react';
import { DaisyPlayer } from 'a11y-player';
function AutoSavePlayer() {
const [lastSaved, setLastSaved] = useState(null);
const handleTimeUpdate = useCallback((currentTime, bookmark, isPlaying) => {
// Auto-save every 30 seconds during playback
if (isPlaying && currentTime % 30 === 0) {
localStorage.setItem('autoSaveBookmark', bookmark);
setLastSaved(new Date().toLocaleTimeString());
}
}, []);
return (
<>
<DaisyPlayer
dirUrl="https://example.com/daisy-books/book1"
appUrl="https://myapp.com"
onTimeUpdate={handleTimeUpdate}
timeUpdateInterval={1000} // Check every second
onPlaybackStateChange={(state) => {
console.log('Playback state:', state);
}}
/>
{lastSaved && <p>Last auto-saved: {lastSaved}</p>}
</>
);
}
```
### With Programmatic Control
```jsx
import { useRef } from 'react';
import { DaisyPlayer, DaisyPlayerRef } from 'a11y-player';
function ControlledPlayer() {
const playerRef = useRef<DaisyPlayerRef>(null);
const handleJumpToChapter = (chapterBookmark) => {
playerRef.current?.seek(chapterBookmark);
};
const handleTogglePlayback = () => {
playerRef.current?.togglePlayPause();
};
const getCurrentInfo = () => {
const currentTime = playerRef.current?.getCurrentTime();
const bookmark = playerRef.current?.getCurrentBookmark();
console.log(`Current time: ${currentTime}, Bookmark: ${bookmark}`);
};
return (
<>
<DaisyPlayer
ref={playerRef}
dirUrl="https://example.com/daisy-books/book1"
appUrl="https://myapp.com"
/>
<div>
<button onClick={handleTogglePlayback}>Toggle Play/Pause</button>
<button onClick={getCurrentInfo}>Get Current Info</button>
<button onClick={() => handleJumpToChapter('chapter1.smil:0')}>Jump to Chapter 1</button>
</div>
</>
);
}
```
### With Event Callbacks
```jsx
import { DaisyPlayer } from 'a11y-player';
function EventAwarePlayer() {
return (
<DaisyPlayer
dirUrl="https://example.com/daisy-books/book1"
appUrl="https://myapp.com"
onPlay={() => console.log('Playback started')}
onPause={() => console.log('Playback paused')}
onSeek={(bookmark) => console.log('Seeked to:', bookmark)}
onBookmarkChange={(bookmark) => {
// Send to analytics or save to database
fetch('/api/progress', {
method: 'POST',
body: JSON.stringify({ bookmark }),
headers: { 'Content-Type': 'application/json' }
});
}}
/>
);
}
```
## DAISY Format Requirements
The DAISY Player requires a backend server that hosts DAISY format books with the following specifications:
1. **Required Files**:
- `ncc.html`: The Navigation Control Center file containing the book's structure and metadata
- SMIL files (`.smil`): Files describing the synchronization between text and audio
- Audio files (typically MP3): The actual audio content referenced by the SMIL files
2. **File Access**:
The application directly accesses files using these URL patterns:
- `${dirUrl}/ncc.html` - For book structure and metadata
- `${dirUrl}/{smilFile}` - For specific SMIL files
- `${dirUrl}/{audioFile}` - For audio files
3. **CORS Configuration**:
The backend must allow cross-origin requests from the frontend application domain.
## Keyboard Shortcuts
- **Space** or **K**: Play/pause
- **J** or **Left Arrow**: Rewind 10 seconds
- **L** or **Right Arrow**: Forward 10 seconds
- **Up Arrow**: Increase playback speed
- **Down Arrow**: Decrease playback speed
- **I**: Toggle section list view
- **Escape**: Close section list view (when open)
Keyboard shortcuts are only active when the player container (or any of its
controls) has keyboard focus. Use the **Tab** key or click on the player to
focus it before using these shortcuts.
## Accessibility
This player is designed with accessibility as a primary concern:
- Fully keyboard navigable
- ARIA attributes for screen reader compatibility
- High contrast visuals
- Keyboard shortcuts for common actions
- Focus management between different views
## Development
### Prerequisites
- Node.js (v14 or higher)
- npm or yarn
### Setup
1. Clone the repository
2. Install dependencies:
```
npm install
```
3. Start the development server:
```
npm run dev
```
### Building for Production
```
npm run build
```
## API Reference
### Types
```typescript
// Main component props
export interface DaisyPlayerProps {
dirUrl: string;
appUrl: string;
pathPrefix?: string;
initialBookmark?: string;
className?: string;
language?: string;
onTimeUpdate?: (currentTime: number, bookmark: string, isPlaying: boolean) => void;
timeUpdateInterval?: number;
onBookmarkChange?: (bookmark: string) => void;
onPlaybackStateChange?: (state: PlaybackState) => void;
onPlay?: () => void;
onPause?: () => void;
onSeek?: (bookmark: string) => void;
}
// Router component props
export interface DaisyPlayerWithRouterProps extends DaisyPlayerProps {
bookmarkParam?: string;
}
// Ref interface for programmatic control
export interface DaisyPlayerRef {
getCurrentTime(): number;
getDuration(): number;
isPlaying(): boolean;
getCurrentBookmark(): string;
getPlaybackRate(): number;
togglePlayPause(): void;
seek(bookmark: string): void;
}
// Playback state structure
interface PlaybackState {
currentTime: number;
duration: number;
isPlaying: boolean;
currentBookmark: string; // Format: "smilFile:timeInSeconds"
playbackRate: number;
}
```
## Changelog
### v0.2.0
- Added `onTimeUpdate` callback with configurable interval
- Added `onPlaybackStateChange` callback for comprehensive state monitoring
- Added `DaisyPlayerRef` interface for programmatic control
- Added individual event callbacks: `onPlay`, `onPause`, `onSeek`
- Enhanced `onBookmarkChange` callback (now properly typed)
- Added `ref` support to both `DaisyPlayer` and `DaisyPlayerWithRouter`
- Improved TypeScript definitions and exports
## License
[MIT License](LICENSE)