feat: implement airplay

This commit is contained in:
shinya
2025-06-25 22:24:58 +08:00
parent 9e5fa818e7
commit dbbed5e38f
3 changed files with 40 additions and 3 deletions

View File

@@ -25,6 +25,7 @@
"framer-motion": "^12.18.1",
"hls.js": "^1.6.5",
"lucide-react": "^0.438.0",
"media-icons": "^1.1.5",
"next": "^14.2.23",
"next-pwa": "^5.6.0",
"react": "^18.2.0",

9
pnpm-lock.yaml generated
View File

@@ -29,6 +29,9 @@ importers:
lucide-react:
specifier: ^0.438.0
version: 0.438.0(react@18.3.1)
media-icons:
specifier: ^1.1.5
version: 1.1.5
next:
specifier: ^14.2.23
version: 14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -3543,6 +3546,10 @@ packages:
resolution: {integrity: sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==}
engines: {node: '>=16'}
media-icons@1.1.5:
resolution: {integrity: sha512-zu3CjKRs63ybLLWPomRRgTDyYiSrk2bRRgw97ZmN3FGXuo9qUUhBSfYwnjmvSdLG2JOJfAwzDz99bPATSY81DQ==}
engines: {node: '>=16'}
meow@8.1.2:
resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==}
engines: {node: '>=10'}
@@ -9192,6 +9199,8 @@ snapshots:
media-captions@1.0.4: {}
media-icons@1.1.5: {}
meow@8.1.2:
dependencies:
'@types/minimist': 1.2.5

View File

@@ -2,11 +2,19 @@
'use client';
import { MediaPlayer, MediaProvider } from '@vidstack/react';
import {
type MediaProviderAdapter,
AirPlayButton,
isHLSProvider,
MediaPlayer,
MediaProvider,
} from '@vidstack/react';
import { AirPlayIcon } from '@vidstack/react/icons';
import {
defaultLayoutIcons,
DefaultVideoLayout,
} from '@vidstack/react/player/layouts/default';
import Hls from 'hls.js';
import { Heart } from 'lucide-react';
import Head from 'next/head';
import { useSearchParams } from 'next/navigation';
@@ -1125,6 +1133,20 @@ function PlayPageClient() {
);
};
const onProviderChange = (provider: MediaProviderAdapter | null) => {
class extendedHls extends Hls {
attachMedia(media: HTMLMediaElement): void {
super.attachMedia(media);
media.disableRemotePlayback = false;
media.autoplay = true;
}
}
if (isHLSProvider(provider)) {
provider.library = extendedHls;
}
};
return (
<>
<Head>
@@ -1204,6 +1226,7 @@ function PlayPageClient() {
onTimeUpdate={onTimeUpdate}
onPause={saveCurrentPlayProgress}
onError={handlePlayerError}
onProviderChange={onProviderChange}
>
<MediaProvider />
<PlayerUITopbar
@@ -1220,11 +1243,11 @@ function PlayPageClient() {
icons={defaultLayoutIcons}
slots={{
googleCastButton: null,
airPlayButton: null,
pipButton: null,
settingsMenu: null,
muteButton: null, // 隐藏静音按钮
volumeSlider: null, // 隐藏音量条
airPlayButton: null, // 隐藏默认 AirPlay 按钮
beforeCurrentTime:
totalEpisodes > 1 ? (
// 下一集按钮放在时间显示前
@@ -1259,6 +1282,10 @@ function PlayPageClient() {
</button>
)}
<PlaybackRateButton playerRef={playerRef} />
{/* 自定义 AirPlay 按钮 */}
<AirPlayButton className='vds-button'>
<AirPlayIcon className='vds-icon' />
</AirPlayButton>
</>
),
}}
@@ -1623,7 +1650,7 @@ const PlaybackRateButton = ({
};
return (
<button className='vds-button' onClick={cycleRate}>
<button className='vds-button mr-2' onClick={cycleRate}>
{rate === 1 ? '倍速' : `${rate.toFixed(2)}x`}
</button>
);