-
Notifications
You must be signed in to change notification settings - Fork 2
๐ HLS๋ก ์์ ์ฃผ๊ณ ๋ฐ๊ธฐ
- Client์์
.m3u8
ํ์ผ ์์ฒญ - Server์์ NCP Object Storage์ ์ ๊ทผํด ํด๋น m3u8 ํ์ผ์ ๊ฐ์ ธ์ด
- ๊ฐ์ ธ์จ ํ, Server๋ ๋ฐฉ์ ์
์ฅํ ์๊ฐ(TimeStamp) ๊ธฐ์ค์ผ๋ก ์๋ง์ ์์
์ ํ์ํ ์ธ๊ทธ๋จผํธ๋ค๋ง ๋ด๊ธด
.m3u8
ํ์ผ์ ์๋ต - Client๋ ์๋ต๋ฐ์
.m3u8
ํ์ผ์ ๋ฐ์ HLS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ์๋์ผ๋ก.ts
์ธ๊ทธ๋จผํธ ์์ฒญ์ ๋ณด๋ - Server๋ NCP Object Stroage์ ์ ๊ทผํด ํด๋น tsํ์ผ๋ค์ ์๋ตํจ
ํ์ฌ๋ NCP Object Storage์ ์ ๊ทผํด์ m3u8ํ์ผ์ ๊ฐ์ ธ์จ๋ค. ์ฌ๊ธฐ์, ๊ทธ ๋ฐฉ์ ์๋ ๋ชจ๋ ์ฌ์ฉ์๊ฐ ํ์ผ์ ๊ฐ์ ธ์ค๋ API๋ฅผ ์ฐ๋ฅผํ ๋ฐ,
์ด๋ ์ ์ ๊ฐ N๋ช ์ด๋ผ ๊ฐ์ ํ๋ฉด, Object Stroage์ N๋ฒ ์ ๊ทผํ๋ค. ๋ฐ๋ผ์, ํธ๋ํฝ ์ฆ๊ฐ์ ์ฐ๋ ค๊ฐ ์๋ค.
๋ฐ๋ผ์, ์บ์ฑ์ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค.
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { S3 } from 'aws-sdk';
import { Cache } from 'cache-manager';
export class MusicService {
private readonly s3: S3;
private readonly SEGMENT_DURATION = 2;
constructor(
private readonly configService: ConfigService,
@Inject(CACHE_MANAGER) private cacheManager: Cache, // nestJS์ Cache Manager ์ฌ์ฉ
)
const response = await this.s3.getObject(params).promise();
originalM3u8 = response.Body.toString();
await this.cacheManager.set( // ์บ์ฑ
originalM3u8CacheKey,
originalM3u8,
3600000,
);
์บ์ฑ์ ์ฌ์ฉํ๋ฉด, ์๋ณธ .m3u8
ํ์ผ (๋ชจ๋ ์ธ๊ทธ๋จผํธ๊ฐ ๋ค์ด๊ฐ์์)์ ์ฌ์ฉํ์ฌ, ์ ์ ๋ง๋ค ์
์ฅ์๊ฐ์ ๊ณ ๋ คํ์ฌ ๋ค๋ฅธ m3u8ํ์ผ์ ์๋ตํด์ฃผ๋ฉด ๋๋ค.
๋ํ, segment ํ์ผ๋ค๋ ์๋ณธ์ ์บ์ฑ์ผ๋ก ์ ์ฅํ๊ณ , ๊ฐ์ ธ๋ค ์ฐ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์ด๋ฐ์๋, ์ ์ ์๊ฒ ํ์ํ m3u8์ ์๋ตํด์ฃผ๋ ๊ณผ์ ์์, m3u8์ ํ์ฑ ํ, ์ฌ๊ตฌ์ฑํ๋ ๊ฒ์ผ๋ก ๊ฒฐ์ ํ์๋ค.
๋ค๋ง, ํ์ฑ ๊ณผ์ ์์์ ๊ต์ฅํ ์ค๋ฒํค๋๊ฐ ์ฐ๋ ค๊ฐ ๋๋ ์ํฉ์ด์ด์, ๋์์ ์ฐพ์๋ณด์๋ค.
- Server
@Injectable()
export class MusicService {
async generateMusicInfo(joinTimeStamp: number): Promise<{
m3u8Url: string;
startSegment: number;
}> {
const currentSong = await this.findCurrentSong(joinTimeStamp);
if (!currentSong) {
throw new Error('No song is currently playing');
}
const elapsedTime = joinTimeStamp - currentSong.startTime;
const elapsedSeconds = Math.floor(elapsedTime / 1000);
const startSegment = Math.floor(elapsedSeconds / this.SEGMENT_DURATION);
return {
m3u8Url: `${currentSong.id}/playlist.m3u8`,
startSegment
};
}
}
- Client
const hls = new Hls({
startPosition: startSegment // ์์ ์ธ๊ทธ๋จผํธ ์ง์
});
hls.loadSource(m3u8Url);
startPosition
์ด๋ผ๋ ์ต์
์ ์ฌ์ฉํ์ฌ ์์ ์ธ๊ทธ๋จผํธ๋ฅผ ์ง์ ํด์ค ์ ์์๊ณ , ์ด๋ Server์์ ์์๋๋ ์ธ๊ทธ๋จผํธ๋ฅผ ์ ํด์ฃผ๋ฏ๋ก ํ์ฑ ๊ณผ์ ์ ๊ฑฐ์น์ง ์์๋ ๋์๋ค.
์ฐพ์๋ณด๋ currnet ์ง์ ์ ํ์ธํ๋ api๋ฅผ ํ ๊ฐ ๋ ์์ฑํด์ค์ผํ๋ ๊ฒ์ด์๋ค.
๊ทธ๋ฅ ํ์ฑํ๋ ๊ฒ์ด ํจ์ฌ ์ค๋ฒํค๋๊ฐ ์ ๋ค๊ณ ๊ฒฐ๋ก ์ ์ง์๋ค. ๊ฒฐ๊ตญ ๊ทธ๋ฅ ํ์ฑํ๋ ๊ฒ์ผ๋ก ๋์๊ฐ์ผํ๋ค..
์ ์ฅ ์๊ฐ์ ๊ดํด์ ์ด๋ป๊ฒ ๊ณ์ฐ์ ํด์ผํ ์ง ๋ง๋งํ๋ค.
๋ฐฉ ์
์ฅ์๊ฐ์ ๊ธฐ๋กํ JoinTimeStamp
๋ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์์ค์ง๋ง, ์์
์์ ์๊ฐ์ ๋ฐ์์ฌ ๋ฐฉ๋ฒ์ด ๋ ์ค๋ฅด์ง ์์๋ค.
์ค์๋๊ป ์ฌ์ญค๋ณธ ๊ฒฐ๊ณผ, ๊ด๋ฆฌ์๊ฐ ๋ฐฉ์ ์์ฑํ ๋ ์์ ์์ ์ ๋ํ ๋ฉํ๋ฐ์ดํฐ๋ฅผ Redis์ ์ ์ฅํ๋ ๋ฐฉ์์ ์ ์ํด์ฃผ์ จ๋ค.
์ฌ๊ธฐ์ ํฅ์, ์ ์ฅํ ๋, ๊ฐ ์์ ์ ๋ํ ์์ ์๊ฐ๋ ์ ์ฅํ๋ค๋ ๊ฒ.
๊ฒฐ๊ตญ Redis์๋ ์๋์ ๊ฐ์ ํ์์ผ๋ก ์ ์ฅ์ด ๋ ๊ฒ ๊ฐ๋ค.
songs: [
{
id: "song1",
startTime: 1710810000000, // 2024-03-19 10:00:00
duration: 180 // 3๋ถ
},
{
id: "song2",
startTime: 1710810180000, // 2024-03-19 10:03:00
duration: 200 // 3๋ถ 20์ด
}
]
๋ฐ๋ผ์ ํด์ค์ผํ๋ ์์ ์
- ์ ์ ๊ฐ ๋ค์ด์จ ์๊ฐ๊ณผ ๊ฐ ๋ ธ๋์ startTIme๋ค์ ๋น๊ตํด์ ํ์ฌ ์ฌ์๋์ด์ผ ํ๋ ์๋ง์ ๋ ธ๋๋ฅผ ์ฐพ๊ณ ,
- ๊ทธ ๋ ธ๋์ startTime๊ณผ ๋ฐฉ ์ ์ฅ ์๊ฐ์ ๋น๊ตํด์ segment ํฌ๊ธฐ(2์ด?)๋ก ๋๋ ๋ค ์๋ง์ startSegment๋ฅผ ์ง์ ํด์ค๋ค.
KEY: "rooms:{roomId}:session"
HASH Fields:
- releaseTimestamp: ์คํธ๋ฆฌ๋ฐ ์ธ์
์์ ํ์ ์คํฌํ
- songs: ๊ฐ ๋
ธ๋๋ค์ duration์ ๋ด์ ๋ฐฐ์ด []
rooms:${albumId}:session { releaseTimestamp "123123123" songs "[97.812563,84.12]" }
song๋ง๋ค ์ ์ฅํ๋ ๋ฐฉ์์์ ํ๋์ album์์ ์ฌ๋ฌ ๊ฐ์ ๋ ธ๋๊ฐ ์กด์ฌํ๊ณ , ๊ทธ ๋ ธ๋์ ํ๋ ์ด ํ์์ ์ ์ฅํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํ์์ต๋๋ค.
๋ฐ๋ผ์, releaseTimestamp๋ฅผ ๋ฐ์์์, duration ์ ๋ณด๋ฅผ ๋ฐ๋ณต์ ๋๋ฆฌ๋ฉด์ ์ด๋ค ๋ ธ๋์ ์ด๋์ฏค ํ์ด์ค์ผํ๋์ง๋ฅผ ๊ณ์ฐํ๋ ๋ก์ง์ ์์ฑํ์ต๋๋ค.
์ด๋ค ๋
ธ๋๋ songIndex
๋ก ๊ตฌ๋ถ์ ์ง์์ต๋๋ค.
์ถํ ๊ตฌํ ์์
๐ ffmpeg๋ stderr๋ก ๋๋ฒ๊น
์ ํ๋ ์ด์
๐ HLS ํ๋กํ ์ฝ์ ๊ดํ ์ ๋ฆฌ ๋ฐ FFmpeg ์ฌ์ฉ๊ธฐ
๐ ๋นํธ๋ tsconfig.json์ด ์ธ ๊ฐ?
๐ NestJS ๊ธฐ๋ณธ ๊ฐ๋
- Modules
๐ Socket.io ์ต(๊ฐ)์ ํ
๐ ๋์ปค์ nginx์ ์ฌ์ฉ๊ธฐ
๐ ๋ถํํ
์คํธ๋ฅผ ํด๋ณด์
๐ FSD ์ฌ์ฉ๊ธฐ, ๊ทผ๋ฐ ์ด์ ๋๋ง์ ๊ท์น์ ๊ณ๋ค์ธ
๐ CICD ๊ตฌ์กฐ ์์
๐ ์จ๋ฒ ๋จ์๋ก ์คํธ๋ฆฌ๋ฐ ํ๊ธฐ (with HLS)
๐ HLS๋ก ์์
์ฃผ๊ณ ๋ฐ๊ธฐ
๐ vite + react + typescript ํ๊ฒฝ์์ path alias ์ค์
๐ React Scan์ด ๋ญ์ฃ ?
๐ ๋ก์ปฌ ํ๊ฒฝ ๊ฐ๋ฐ ๋ชจ๋ ๋ฐฐํฌ
๐ ์จ๋ฒ ์ ์ฒด๋ฅผ ์คํธ๋ฆฌ๋ฐํ๋ค๊ณ ? (with HLS)
๐ ์ฝ๋์ ์์ ์ฑ์ ๋์ด๊ธฐ ์ํด ํ
์คํธ์ฝ๋๋ฅผ ์์ฑํด๋ณด์
๐ ์๋ก๊ณ ์นจ ์ HLS ERROR
๐ input ํ๊ทธ์ ํ๊ธ ์
๋ ฅ ํ, Enter๋ฅผ ๋๋ฅด๋ฉด ํจ์๊ฐ ๋๋ฒ ํธ์ถ๋๋ ์ค๋ฅ
๐ nginx proxy pass๋ฅผ ๋ฐ๊ฟจ๋๋ ์๊ธด ์๋ฌ - ์ค์จ๊ฑฐ ์ธ์ ๋ฌธ์
๐ ๋ฐฐํฌ ํ๊ฒฝ์์ ํด๋ผ์ด์ธํธ-์๋ฒ WS handshake
๐ ๋ ๋๋ง ๋ฒ์ธ์ ํ๋!
๐ ๊ทธ๋ผ์ด๋ ๋ฃฐ
๐ฅ ํ์ ์๊ฐ
๐ ์ฝ๋ & ๊น ์ปจ๋ฒค์
๐ณ ๊น branch ์ ๋ต
๐ ๋
ธ์
๋ฌธ์ ์ ์ฅ์
๐จ ํผ๊ทธ๋ง
๐งโ๐ป ๊ธฐํ ๊ณต์ ๋ฐํ ์๋ฃ
๐ค 2์ฃผ์ฐจ ๋ฐํ ์๋ฃ
๐ ๋ฐฑ๋ก๊ทธ
๐ 1์ฃผ์ฐจ
๐ 2์ฃผ์ฐจ
๐ 3์ฃผ์ฐจ
๐ 4์ฃผ์ฐจ
๐ 5์ฃผ์ฐจ
๐๏ธ 1์ฃผ์ฐจ
๐๏ธ 2์ฃผ์ฐจ
๐๏ธ 3์ฃผ์ฐจ
๐๏ธ 4์ฃผ์ฐจ
๐๏ธ 5์ฃผ์ฐจ
โจ 1์ฃผ์ฐจ
โจ 2์ฃผ์ฐจ
โจ 3์ฃผ์ฐจ
โจ 4์ฃผ์ฐจ