mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Audio playing working
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -8,6 +8,8 @@ import AttachmentVideo from '@root/components/Timelines/Timeline/Shared/Attachme
|
||||
import { IImageInfo } from 'react-native-image-zoom-viewer/built/image-viewer.type'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import AttachmentUnsupported from './Attachment/Unsupported'
|
||||
import AttachmentAudio from './Attachment/Audio'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
export interface Props {
|
||||
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
|
||||
@ -21,13 +23,6 @@ const TimelineAttachment: React.FC<Props> = ({ status, contentWidth }) => {
|
||||
const onPressBlurView = useCallback(() => {
|
||||
setSensitiveShown(false)
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (status.sensitive && sensitiveShown === false) {
|
||||
setTimeout(() => {
|
||||
setSensitiveShown(true)
|
||||
}, 10000)
|
||||
}
|
||||
}, [sensitiveShown])
|
||||
|
||||
let imageUrls: (IImageInfo & {
|
||||
preview_url: Mastodon.AttachmentImage['preview_url']
|
||||
@ -83,7 +78,13 @@ const TimelineAttachment: React.FC<Props> = ({ status, contentWidth }) => {
|
||||
/>
|
||||
)
|
||||
case 'audio':
|
||||
return <Text key={index}>音频不支持</Text>
|
||||
return (
|
||||
<AttachmentAudio
|
||||
key={index}
|
||||
sensitiveShown={sensitiveShown}
|
||||
audio={attachment}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return <AttachmentUnsupported key={index} attachment={attachment} />
|
||||
}
|
||||
@ -100,23 +101,43 @@ const TimelineAttachment: React.FC<Props> = ({ status, contentWidth }) => {
|
||||
>
|
||||
{attachments}
|
||||
|
||||
{status.sensitive && sensitiveShown && (
|
||||
<Pressable style={styles.sensitiveBlur}>
|
||||
{status.sensitive &&
|
||||
(sensitiveShown ? (
|
||||
<Pressable style={styles.sensitiveBlur}>
|
||||
<Pressable
|
||||
onPress={onPressBlurView}
|
||||
style={[
|
||||
styles.sensitiveBlurButton,
|
||||
{ backgroundColor: theme.backgroundOverlay }
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[styles.sensitiveText, { color: theme.primaryOverlay }]}
|
||||
>
|
||||
显示敏感内容
|
||||
</Text>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
) : (
|
||||
<Pressable
|
||||
onPress={onPressBlurView}
|
||||
style={[
|
||||
styles.sensitiveBlurButton,
|
||||
{ backgroundColor: theme.backgroundOverlay }
|
||||
{
|
||||
backgroundColor: theme.backgroundOverlay,
|
||||
position: 'absolute',
|
||||
top: StyleConstants.Spacing.S,
|
||||
left: StyleConstants.Spacing.S
|
||||
}
|
||||
]}
|
||||
onPress={() => setSensitiveShown(!sensitiveShown)}
|
||||
>
|
||||
<Text
|
||||
style={[styles.sensitiveText, { color: theme.primaryOverlay }]}
|
||||
>
|
||||
显示敏感内容
|
||||
</Text>
|
||||
<Feather
|
||||
name='eye-off'
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme.primaryOverlay}
|
||||
/>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
)}
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
123
src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx
Normal file
123
src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Image, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { Audio } from 'expo-av'
|
||||
import { ButtonRow } from '@components/Button'
|
||||
import layoutAnimation from '@root/utils/styles/layoutAnimation'
|
||||
import { Surface } from 'gl-react-expo'
|
||||
import { Blurhash } from 'gl-react-blurhash'
|
||||
import Slider from '@react-native-community/slider'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
|
||||
export interface Props {
|
||||
sensitiveShown: boolean
|
||||
audio: Mastodon.AttachmentAudio
|
||||
}
|
||||
|
||||
const AttachmentAudio: React.FC<Props> = ({ sensitiveShown, audio }) => {
|
||||
layoutAnimation()
|
||||
|
||||
const { theme } = useTheme()
|
||||
|
||||
const [audioPlayer, setAudioPlayer] = useState<Audio.Sound>()
|
||||
const [audioPlaying, setAudioPlaying] = useState(false)
|
||||
const [audioPosition, setAudioPosition] = useState(0)
|
||||
const playAudio = useCallback(async () => {
|
||||
if (!audioPlayer) {
|
||||
await Audio.setAudioModeAsync({ interruptionModeIOS: 1 })
|
||||
const { sound } = await Audio.Sound.createAsync(
|
||||
{ uri: audio.url },
|
||||
{},
|
||||
// @ts-ignore
|
||||
props => setAudioPosition(props.positionMillis)
|
||||
)
|
||||
setAudioPlayer(sound)
|
||||
} else {
|
||||
await audioPlayer.setPositionAsync(audioPosition)
|
||||
audioPlayer.playAsync()
|
||||
setAudioPlaying(true)
|
||||
}
|
||||
}, [audioPlayer, audioPosition])
|
||||
const pauseAudio = useCallback(async () => {
|
||||
audioPlayer!.pauseAsync()
|
||||
setAudioPlaying(false)
|
||||
}, [audioPlayer])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pressable style={styles.overlay}>
|
||||
{sensitiveShown ? (
|
||||
audio.blurhash && (
|
||||
<Surface
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<Blurhash hash={audio.blurhash} />
|
||||
</Surface>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{(audio.preview_url || audio.preview_remote_url) && (
|
||||
<Image
|
||||
style={styles.background}
|
||||
source={{ uri: audio.preview_url || audio.preview_remote_url }}
|
||||
/>
|
||||
)}
|
||||
<ButtonRow
|
||||
icon={audioPlaying ? 'pause' : 'play'}
|
||||
size='L'
|
||||
{...(audioPlaying
|
||||
? { onPress: pauseAudio }
|
||||
: { onPress: playAudio })}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
alignSelf: 'flex-end',
|
||||
backgroundColor: theme.backgroundOverlay,
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingVertical: StyleConstants.Spacing.XS,
|
||||
borderRadius: 6,
|
||||
opacity: sensitiveShown ? 0.35 : undefined
|
||||
}}
|
||||
>
|
||||
<Slider
|
||||
style={{
|
||||
width: '100%'
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={audio.meta.original.duration * 1000}
|
||||
value={audioPosition}
|
||||
minimumTrackTintColor={theme.secondary}
|
||||
maximumTrackTintColor={theme.disabled}
|
||||
onSlidingStart={() => {
|
||||
audioPlayer?.pauseAsync()
|
||||
setAudioPlaying(false)
|
||||
}}
|
||||
onSlidingComplete={value => {
|
||||
setAudioPosition(value)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
background: { position: 'absolute', width: '100%', height: '100%' },
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
})
|
||||
|
||||
export default AttachmentAudio
|
@ -6,7 +6,7 @@ import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import openLink from '@root/utils/openLink'
|
||||
|
||||
export interface Props {
|
||||
attachment: Mastodon.Attachment
|
||||
attachment: Mastodon.AttachmentUnknown
|
||||
}
|
||||
|
||||
const AttachmentUnsupported: React.FC<Props> = ({ attachment }) => {
|
||||
|
Reference in New Issue
Block a user