mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-05-22 14:37:30 +08:00
feat: implement airplay
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
"framer-motion": "^12.18.1",
|
"framer-motion": "^12.18.1",
|
||||||
"hls.js": "^1.6.5",
|
"hls.js": "^1.6.5",
|
||||||
"lucide-react": "^0.438.0",
|
"lucide-react": "^0.438.0",
|
||||||
|
"media-icons": "^1.1.5",
|
||||||
"next": "^14.2.23",
|
"next": "^14.2.23",
|
||||||
"next-pwa": "^5.6.0",
|
"next-pwa": "^5.6.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -29,6 +29,9 @@ importers:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.438.0
|
specifier: ^0.438.0
|
||||||
version: 0.438.0(react@18.3.1)
|
version: 0.438.0(react@18.3.1)
|
||||||
|
media-icons:
|
||||||
|
specifier: ^1.1.5
|
||||||
|
version: 1.1.5
|
||||||
next:
|
next:
|
||||||
specifier: ^14.2.23
|
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)
|
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==}
|
resolution: {integrity: sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
media-icons@1.1.5:
|
||||||
|
resolution: {integrity: sha512-zu3CjKRs63ybLLWPomRRgTDyYiSrk2bRRgw97ZmN3FGXuo9qUUhBSfYwnjmvSdLG2JOJfAwzDz99bPATSY81DQ==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
meow@8.1.2:
|
meow@8.1.2:
|
||||||
resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==}
|
resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -9192,6 +9199,8 @@ snapshots:
|
|||||||
|
|
||||||
media-captions@1.0.4: {}
|
media-captions@1.0.4: {}
|
||||||
|
|
||||||
|
media-icons@1.1.5: {}
|
||||||
|
|
||||||
meow@8.1.2:
|
meow@8.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/minimist': 1.2.5
|
'@types/minimist': 1.2.5
|
||||||
|
|||||||
@@ -2,11 +2,19 @@
|
|||||||
|
|
||||||
'use client';
|
'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 {
|
import {
|
||||||
defaultLayoutIcons,
|
defaultLayoutIcons,
|
||||||
DefaultVideoLayout,
|
DefaultVideoLayout,
|
||||||
} from '@vidstack/react/player/layouts/default';
|
} from '@vidstack/react/player/layouts/default';
|
||||||
|
import Hls from 'hls.js';
|
||||||
import { Heart } from 'lucide-react';
|
import { Heart } from 'lucide-react';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useSearchParams } from 'next/navigation';
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -1204,6 +1226,7 @@ function PlayPageClient() {
|
|||||||
onTimeUpdate={onTimeUpdate}
|
onTimeUpdate={onTimeUpdate}
|
||||||
onPause={saveCurrentPlayProgress}
|
onPause={saveCurrentPlayProgress}
|
||||||
onError={handlePlayerError}
|
onError={handlePlayerError}
|
||||||
|
onProviderChange={onProviderChange}
|
||||||
>
|
>
|
||||||
<MediaProvider />
|
<MediaProvider />
|
||||||
<PlayerUITopbar
|
<PlayerUITopbar
|
||||||
@@ -1220,11 +1243,11 @@ function PlayPageClient() {
|
|||||||
icons={defaultLayoutIcons}
|
icons={defaultLayoutIcons}
|
||||||
slots={{
|
slots={{
|
||||||
googleCastButton: null,
|
googleCastButton: null,
|
||||||
airPlayButton: null,
|
|
||||||
pipButton: null,
|
pipButton: null,
|
||||||
settingsMenu: null,
|
settingsMenu: null,
|
||||||
muteButton: null, // 隐藏静音按钮
|
muteButton: null, // 隐藏静音按钮
|
||||||
volumeSlider: null, // 隐藏音量条
|
volumeSlider: null, // 隐藏音量条
|
||||||
|
airPlayButton: null, // 隐藏默认 AirPlay 按钮
|
||||||
beforeCurrentTime:
|
beforeCurrentTime:
|
||||||
totalEpisodes > 1 ? (
|
totalEpisodes > 1 ? (
|
||||||
// 下一集按钮放在时间显示前
|
// 下一集按钮放在时间显示前
|
||||||
@@ -1259,6 +1282,10 @@ function PlayPageClient() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<PlaybackRateButton playerRef={playerRef} />
|
<PlaybackRateButton playerRef={playerRef} />
|
||||||
|
{/* 自定义 AirPlay 按钮 */}
|
||||||
|
<AirPlayButton className='vds-button'>
|
||||||
|
<AirPlayIcon className='vds-icon' />
|
||||||
|
</AirPlayButton>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
@@ -1623,7 +1650,7 @@ const PlaybackRateButton = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className='vds-button' onClick={cycleRate}>
|
<button className='vds-button mr-2' onClick={cycleRate}>
|
||||||
{rate === 1 ? '倍速' : `${rate.toFixed(2)}x`}
|
{rate === 1 ? '倍速' : `${rate.toFixed(2)}x`}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user