mirror of
https://github.com/tooot-app/app
synced 2025-01-06 23:01:51 +01:00
Audio playing working
This commit is contained in:
parent
6eeb86921c
commit
b5b9c0669c
@ -11,6 +11,7 @@
|
||||
"@expo/vector-icons": "^12.0.0",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-native-community/segmented-control": "2.2.1",
|
||||
"@react-native-community/slider": "3.0.3",
|
||||
"@react-navigation/bottom-tabs": "^5.11.2",
|
||||
"@react-navigation/native": "^5.8.10",
|
||||
"@reduxjs/toolkit": "^1.5.0",
|
||||
@ -19,6 +20,7 @@
|
||||
"expo": "^40.0.0",
|
||||
"expo-auth-session": "~3.0.0",
|
||||
"expo-av": "~8.7.0",
|
||||
"expo-blur": "~8.2.2",
|
||||
"expo-crypto": "~8.4.0",
|
||||
"expo-gl": "~9.2.0",
|
||||
"expo-image-picker": "~9.2.0",
|
||||
@ -78,4 +80,4 @@
|
||||
"typescript": "~4.0.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
}
|
||||
|
31
src/@types/mastodon.d.ts
vendored
31
src/@types/mastodon.d.ts
vendored
@ -82,7 +82,7 @@ declare namespace Mastodon {
|
||||
| AttachmentVideo
|
||||
| AttachmentGifv
|
||||
| AttachmentAudio
|
||||
// | AttachmentUnknown
|
||||
| AttachmentUnknown
|
||||
|
||||
type AttachmentImage = {
|
||||
// Base
|
||||
@ -187,8 +187,9 @@ declare namespace Mastodon {
|
||||
|
||||
// Others
|
||||
remote_url?: string
|
||||
preview_remote_url?: string // undocumented
|
||||
text_url?: string
|
||||
meta?: {
|
||||
meta: {
|
||||
length: string
|
||||
duration: number
|
||||
audio_encode: string
|
||||
@ -203,20 +204,20 @@ declare namespace Mastodon {
|
||||
blurhash?: string
|
||||
}
|
||||
|
||||
// type AttachmentUnknown = {
|
||||
// // Base
|
||||
// id: string
|
||||
// type: 'unknown'
|
||||
// url: string
|
||||
// preview_url: string
|
||||
type AttachmentUnknown = {
|
||||
// Base
|
||||
id: string
|
||||
type: 'unknown'
|
||||
url: string
|
||||
preview_url: string
|
||||
|
||||
// // Others
|
||||
// remote_url?: string
|
||||
// text_url?: string
|
||||
// meta?: any
|
||||
// description?: string
|
||||
// blurhash?: string
|
||||
// }
|
||||
// Others
|
||||
remote_url?: string
|
||||
text_url?: string
|
||||
meta?: any
|
||||
description?: string
|
||||
blurhash?: string
|
||||
}
|
||||
|
||||
type Card = {
|
||||
// Base
|
||||
|
@ -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 }) => {
|
||||
|
10
yarn.lock
10
yarn.lock
@ -1339,6 +1339,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/segmented-control/-/segmented-control-2.2.1.tgz#5ca418d78c5f6051353c9586918458713b88a83c"
|
||||
integrity sha512-BzxFbI9Iqv+31yVqEvCTzJYmwb8jOMTf/UPuC4Hj176tmEPqBpuDaGH+rkAFg1miOco3/43RQxiAZO+mkY40Fg==
|
||||
|
||||
"@react-native-community/slider@3.0.3":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-3.0.3.tgz#830167fd757ba70ac638747ba3169b2dbae60330"
|
||||
integrity sha512-8IeHfDwJ9/CTUwFs6x90VlobV3BfuPgNLjTgC6dRZovfCWigaZwVNIFFJnHBakK3pW2xErAPwhdvNR4JeNoYbw==
|
||||
|
||||
"@react-navigation/bottom-tabs@^5.11.2":
|
||||
version "5.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-5.11.2.tgz#5b541612fcecdea2a5024a4028da35e4a727bde6"
|
||||
@ -2860,6 +2865,11 @@ expo-av@~8.7.0:
|
||||
lodash "^4.17.15"
|
||||
nullthrows "^1.1.0"
|
||||
|
||||
expo-blur@~8.2.2:
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-8.2.2.tgz#a7643d893afb7aed5512b25d5df22ee6083832c1"
|
||||
integrity sha512-Xiklw60RUPIchHKzfvLTIuccVDTIQEAIPv02yJY2xFDujQKjE0NU0/Z5Z+zsEI9QOi82jX9NbR8gQ+8Mm3hDhA==
|
||||
|
||||
expo-constants@^9.3.3, expo-constants@~9.3.3:
|
||||
version "9.3.5"
|
||||
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-9.3.5.tgz#78085763e8ed100a5f2df7c682fd99631aa03d5e"
|
||||
|
Loading…
Reference in New Issue
Block a user