UNPKG

react-native-audio-api

Version:

react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification

237 lines (236 loc) • 6.87 kB
"use strict"; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { ActivityIndicator, Image, Platform, Pressable, StyleSheet, Text, View } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { useAnimatedRef, useSharedValue } from 'react-native-reanimated'; import { useAudioTagContext } from "../AudioTagContext.js"; import { formatTime, timeFromLocationX, useExpandableTrackHeight } from "./audioControlUtils.js"; import PlayIcon from './icons/play.png'; import PauseIcon from './icons/pause.png'; import VolumeIcon from './icons/speaker.png'; import MuteIcon from './icons/speaker-x.png'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const TRACK_BAR_HEIGHT = 12; const TRACK_BAR_HEIGHT_PRESSED = 18; const TRACK_BAR_ANIM_MS = 150; const SCRUB_PAN_MIN_DISTANCE = 8; const AudioControls = () => { const { ready, play, pause, seekToTime, playbackState, muted, setMuted, currentTime, duration } = useAudioTagContext(); const progressTrackAnim = useExpandableTrackHeight(TRACK_BAR_HEIGHT, TRACK_BAR_HEIGHT_PRESSED, TRACK_BAR_ANIM_MS); const [scrubTime, setScrubTime] = useState(null); const progressTrackRef = useAnimatedRef(); const progressMetricsWidth = useSharedValue(0); const durationRef = useRef(duration); durationRef.current = duration; const onStart = useCallback(x => { progressTrackAnim.expand(); const d = durationRef.current; progressTrackRef.current?.measureInWindow((_left, _y, width, _h) => { progressMetricsWidth.value = width; setScrubTime(timeFromLocationX(x, width, d)); }); }, [progressTrackAnim, progressMetricsWidth, setScrubTime, progressTrackRef]); const onUpdate = useCallback(x => { const d = durationRef.current; const w = progressMetricsWidth.value; setScrubTime(timeFromLocationX(x, w, d)); }, [progressMetricsWidth]); const seekTo = useCallback(x => { const d = durationRef.current; const w = progressMetricsWidth.value; const t = timeFromLocationX(x, w, d); seekToTime(t); }, [progressMetricsWidth, seekToTime]); const onEnd = useCallback(x => { seekTo(x); progressTrackAnim.collapse(); setScrubTime(null); }, [progressTrackAnim, seekTo]); const onCancel = useCallback(() => { progressTrackAnim.collapse(); setScrubTime(null); }, [progressTrackAnim]); const onTapSeek = useCallback(x => { onEnd(x); }, [onEnd]); const scrubGesture = useMemo(() => { const panGesture = Gesture.Pan().runOnJS(true).minDistance(SCRUB_PAN_MIN_DISTANCE).onStart(e => { onStart(e.x); }).onUpdate(e => { onUpdate(e.x); }).onEnd(e => { onEnd(e.x); }).onFinalize((_e, success) => { if (!success) { onCancel(); } }); const tapGesture = Gesture.Tap().runOnJS(true).maxDistance(14).onEnd((e, success) => { if (success) { onTapSeek(e.x); } }); return Gesture.Race(panGesture, tapGesture); }, [onStart, onUpdate, onEnd, onCancel, onTapSeek]); const onPlayPausePress = useCallback(() => { if (playbackState === 'playing') { pause(); } else { play(); } }, [playbackState, pause, play]); const onProgressTrackLayout = useCallback(() => { progressTrackRef.current?.measureInWindow((_left, _y, width, _h) => { progressMetricsWidth.value = width; }); }, [progressMetricsWidth, progressTrackRef]); if (!ready) { return /*#__PURE__*/_jsx(View, { style: styles.container, children: /*#__PURE__*/_jsx(ActivityIndicator, { color: "#333", size: "small" }) }); } const displayTime = scrubTime ?? currentTime; const progress = duration > 0 ? displayTime / duration : 0; return /*#__PURE__*/_jsx(View, { style: styles.container, children: /*#__PURE__*/_jsxs(View, { style: styles.topRow, children: [/*#__PURE__*/_jsx(Pressable, { style: styles.playPause, onPress: onPlayPausePress, children: playbackState === 'playing' ? /*#__PURE__*/_jsx(Image, { source: PauseIcon, style: { width: 24, height: 24 } }) : /*#__PURE__*/_jsx(Image, { source: PlayIcon, style: { width: 24, height: 24 } }) }), /*#__PURE__*/_jsxs(Text, { style: styles.timeText, children: [formatTime(displayTime), " / ", formatTime(duration)] }), /*#__PURE__*/_jsx(GestureDetector, { gesture: scrubGesture, children: /*#__PURE__*/_jsx(View, { ref: progressTrackRef, onLayout: onProgressTrackLayout, style: styles.progressTrack, collapsable: false, children: /*#__PURE__*/_jsx(Animated.View, { style: [styles.trackInner, progressTrackAnim.animatedStyle], children: /*#__PURE__*/_jsx(View, { style: [styles.trackFill, { width: `${progress * 100}%` }] }) }) }) }), /*#__PURE__*/_jsx(Pressable, { style: styles.volumeIcon, onPress: () => setMuted(!muted), children: muted ? /*#__PURE__*/_jsx(Image, { source: MuteIcon, style: { width: 24, height: 24 } }) : /*#__PURE__*/_jsx(Image, { source: VolumeIcon, style: { width: 24, height: 24 } }) })] }) }); }; const styles = StyleSheet.create({ container: { flexDirection: 'column', alignSelf: 'stretch', minWidth: 200, paddingVertical: 10, paddingHorizontal: 12, backgroundColor: '#f5f5f5', borderRadius: 8, borderWidth: 1, borderColor: '#333', ...Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.15, shadowRadius: 4 }, android: { elevation: 4 } }) }, topRow: { flexDirection: 'row', alignItems: 'center' }, playPause: { padding: 4, marginRight: 12 }, timeText: { color: '#000', fontSize: 12, marginRight: 10, minWidth: 48 }, progressTrack: { flex: 1, minWidth: 40, justifyContent: 'center', marginRight: 10 }, trackInner: { alignSelf: 'stretch', backgroundColor: '#ccc', overflow: 'hidden' }, trackFill: { height: '100%', backgroundColor: '#000' }, volumeIcon: { padding: 4, marginRight: 12 }, loadingRow: { flexDirection: 'row', alignItems: 'center' }, loadingText: { color: '#333', fontSize: 14 } }); export default AudioControls; //# sourceMappingURL=AudioControls.js.map