Skip to content

Commit

Permalink
Remove: [Server] LL-HLS ストリーミング機能を削除
Browse files Browse the repository at this point in the history
0.9.0 でライブストリーミング方式を mpegts.js に統一したため不要になった
iOS 17.1 未満向けに残すことも検討はしたが、既に手元に検証環境がなく継続的に両方をサポートし続けるリソースがないことから取りやめた
1年弱前に相当な労力を注ぎ込んで開発しただけに削除するのはとても惜しいが、今後メンテされないコードを残しておく理由もない…
  • Loading branch information
tsukumijima committed Nov 15, 2023
1 parent a61ef97 commit 76d3dd4
Show file tree
Hide file tree
Showing 8 changed files with 16 additions and 1,205 deletions.
2 changes: 1 addition & 1 deletion client/src/utils/PlayerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class PlayerUtils {
if (player.quality === null) {
return '1080p';
}
const regex = /streams\/live\/[a-z0-9-]*\/(.*)\/(mpegts|ll-hls)/;
const regex = /streams\/live\/[a-z0-9-]*\/(.*)\/mpegts/;
const match = player.quality.url.match(regex);
return match ? (match[1] as APIVideoQuality) : '1080p';
}
Expand Down
2 changes: 1 addition & 1 deletion client/src/workers/LivePSIArchivedDataDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class LivePSIArchivedDataDecoder implements ILivePSIArchivedDataDecoder {
public run(decoded_callback: (message: ResponseMessage | IProgramPF) => void): void {

// TS ストリームがデコードされた際のハンドラーをセット
// web-bml には字幕表示機能もあるが、mpegts.js / LL-HLS 側で既に対応しているため敢えて無効化している
// web-bml には字幕表示機能もあるが、mpegts.js 側で既に対応しているため敢えて無効化している
const ts_stream = decodeTS({
// TS ストリームをデコードした結果をメインスレッドの BML ブラウザに送信
sendCallback: (message) => decoded_callback(message),
Expand Down
229 changes: 0 additions & 229 deletions server/app/routers/LiveStreamsRouter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,17 @@
from fastapi import Depends
from fastapi import HTTPException
from fastapi import Path
from fastapi import Query
from fastapi import status
from fastapi.requests import Request
from fastapi.responses import Response
from fastapi.responses import StreamingResponse
from starlette.types import Receive
from sse_starlette.sse import EventSourceResponse
from typing import Literal

from app import schemas
from app.constants import QUALITY, QUALITY_TYPES
from app.models.Channel import Channel
from app.streams.LiveStream import LiveStream
from app.streams.LiveStream import LiveStreamClient
from app.streams.LiveStream import LiveStreamStatus
from app.utils import Logging

Expand Down Expand Up @@ -53,28 +50,6 @@ async def ValidateQuality(quality: str = Path(..., description='映像の品質
return quality


async def GetLiveStreamClient(
display_channel_id: str = Depends(ValidateChannelID),
quality: QUALITY_TYPES = Depends(ValidateQuality),
client_id: str = Path(..., description='ライブストリームのクライアント ID 。'),
) -> LiveStreamClient:
""" ライブストリームのクライアント ID からライブストリームクライアントのインスタンスを取得する """

# 既に接続済みのクライアントのインスタンスを取得
livestream = LiveStream(display_channel_id, quality)
livestream_client = livestream.connectToExistingClient(client_id)

# 指定されたクライアント ID が存在しない
if livestream_client is None:
Logging.error(f'[LiveStreamsRouter][GetLiveStreamClient] Specified client_id was not found [client_id: {client_id}]')
raise HTTPException(
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY,
detail = 'Specified client_id was not found',
)

return livestream_client


@router.get(
'',
summary = 'ライブストリーム一覧 API',
Expand Down Expand Up @@ -381,207 +356,3 @@ async def listen_for_disconnect_monkeypatch(receive: Receive) -> None:
response.listen_for_disconnect = listen_for_disconnect_monkeypatch

return response


# ***** LL-HLS ストリーミング開始/終了 API *****


@router.post(
'/{display_channel_id}/{quality}/ll-hls',
summary = 'ライブ LL-HLS クライアント接続 API',
response_description = 'ライブストリームのクライアント ID。',
response_model = schemas.LiveStreamLLHLSClientID,
)
async def LiveLLHLSClientConnectAPI(
display_channel_id: str = Depends(ValidateChannelID),
quality: QUALITY_TYPES = Depends(ValidateQuality),
):
# ライブストリームに接続し、クライアントのインスタンスを取得
livestream = LiveStream(display_channel_id, quality)
livestream_client = await livestream.connect('ll-hls')

# クライアント ID を返す
return {'client_id': livestream_client.client_id}


@router.delete(
'/{display_channel_id}/{quality}/ll-hls/{client_id}',
summary = 'ライブ LL-HLS クライアント接続切断 API',
status_code = status.HTTP_204_NO_CONTENT,
)
async def LiveLLHLSClientDisconnectAPI(
display_channel_id: str = Depends(ValidateChannelID),
quality: QUALITY_TYPES = Depends(ValidateQuality),
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
):
# ライブストリームへの接続を切断する
livestream = LiveStream(display_channel_id, quality)
livestream.disconnect(livestream_client)


# ***** LL-HLS ストリーミング API (主音声) *****


@router.get(
'/{display_channel_id}/{quality}/ll-hls/{client_id}/primary-audio/playlist.m3u8',
summary = 'ライブ LL-HLS M3U8 プレイリスト API (主音声)',
response_class = Response,
responses = {
status.HTTP_200_OK: {
'description': 'LL-HLS の M3U8 プレイリスト。',
'content': {'application/vnd.apple.mpegurl': {}},
}
}
)
async def LiveLLHLSPrimaryAudioPlaylistAPI(
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
_HLS_msn: int | None = Query(None, description='LL-HLS プレイリストの msn (Media Sequence Number) インデックス。'),
_HLS_part: int | None = Query(None, description='LL-HLS プレイリストの part (部分セグメント) インデックス。'),
):
# クライアントから LL-HLS プレイリストのレスポンスを取得してそのまま返す
return await livestream_client.getPlaylist(_HLS_msn, _HLS_part, secondary_audio=False)


@router.get(
'/{display_channel_id}/{quality}/ll-hls/{client_id}/primary-audio/segment',
summary = 'ライブ LL-HLS セグメントデータ API (主音声)',
response_class = Response,
responses = {
status.HTTP_200_OK: {
'description': 'LL-HLS のセグメントデータ (m4s) 。',
'content': {'video/mp4': {}},
}
}
)
async def LiveLLHLSPrimaryAudioSegmentAPI(
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
msn: int | None = Query(None, description='LL-HLS セグメントの msn (Media Sequence Number) インデックス。'),
):
# クライアントから LL-HLS セグメントデータのレスポンスを取得してそのまま返す
return await livestream_client.getSegment(msn, secondary_audio=False)


@router.get(
'/{display_channel_id}/{quality}/ll-hls/{client_id}/primary-audio/part',
summary = 'ライブ LL-HLS 部分セグメントデータ API (主音声)',
response_class = Response,
responses = {
status.HTTP_200_OK: {
'description': 'LL-HLS の部分セグメントデータ (m4s) 。',
'content': {'video/mp4': {}},
}
}
)
async def LiveLLHLSPrimaryAudioPartialSegmentAPI(
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
msn: int | None = Query(None, description='LL-HLS セグメントの msn (Media Sequence Number) インデックス。'),
part: int | Literal[''] | None = Query(None, description='LL-HLS セグメントの part (部分セグメント) インデックス。'),
):
# part が空文字列の場合は 0 に変換する
if part == '':
part = 0

# クライアントから LL-HLS 部分セグメントデータのレスポンスを取得してそのまま返す
return await livestream_client.getPartialSegment(msn, part, secondary_audio=False)


@router.get(
'/{display_channel_id}/{quality}/ll-hls/{client_id}/primary-audio/init',
summary = 'ライブ LL-HLS 初期セグメントデータ API (主音声)',
response_class = Response,
responses = {
status.HTTP_200_OK: {
'description': 'LL-HLS の初期セグメントデータ (m4s) 。',
'content': {'video/mp4': {}},
}
}
)
async def LiveLLHLSPrimaryAudioInitializationSegmentAPI(
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
):
# クライアントから LL-HLS 初期セグメントデータのレスポンスを取得してそのまま返す
return await livestream_client.getInitializationSegment(secondary_audio=False)


# ***** LL-HLS ストリーミング API (副音声) *****


@router.get(
'/{display_channel_id}/{quality}/ll-hls/{client_id}/secondary-audio/playlist.m3u8',
summary = 'ライブ LL-HLS M3U8 プレイリスト API (副音声)',
response_class = Response,
responses = {
status.HTTP_200_OK: {
'description': 'LL-HLS の M3U8 プレイリスト。',
'content': {'application/vnd.apple.mpegurl': {}},
}
}
)
async def LiveLLHLSSecondaryAudioPlaylistAPI(
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
_HLS_msn: int | None = Query(None, description='LL-HLS プレイリストの msn (Media Sequence Number) インデックス。'),
_HLS_part: int | None = Query(None, description='LL-HLS プレイリストの part (部分セグメント) インデックス。'),
):
# クライアントから LL-HLS プレイリストのレスポンスを取得してそのまま返す
return await livestream_client.getPlaylist(_HLS_msn, _HLS_part, secondary_audio=True)


@router.get(
'/{display_channel_id}/{quality}/ll-hls/{client_id}/secondary-audio/segment',
summary = 'ライブ LL-HLS セグメントデータ API (副音声)',
response_class = Response,
responses = {
status.HTTP_200_OK: {
'description': 'LL-HLS のセグメントデータ (m4s) 。',
'content': {'video/mp4': {}},
}
}
)
async def LiveLLHLSSecondaryAudioSegmentAPI(
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
msn: int | None = Query(None, description='LL-HLS セグメントの msn (Media Sequence Number) インデックス。'),
):
# クライアントから LL-HLS セグメントデータのレスポンスを取得してそのまま返す
return await livestream_client.getSegment(msn, secondary_audio=True)


@router.get(
'/{display_channel_id}/{quality}/ll-hls/{client_id}/secondary-audio/part',
summary = 'ライブ LL-HLS 部分セグメントデータ API (副音声)',
response_class = Response,
responses = {
status.HTTP_200_OK: {
'description': 'LL-HLS の部分セグメントデータ (m4s) 。',
'content': {'video/mp4': {}},
}
}
)
async def LiveLLHLSSecondaryAudioPartialSegmentAPI(
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
msn: int | None = Query(None, description='LL-HLS セグメントの msn (Media Sequence Number) インデックス。'),
part: int | Literal[''] | None = Query(None, description='LL-HLS セグメントの part (部分セグメント) インデックス。'),
):
# part が空文字列の場合は 0 に変換する
if part == '':
part = 0

# クライアントから LL-HLS 部分セグメントデータのレスポンスを取得してそのまま返す
return await livestream_client.getPartialSegment(msn, part, secondary_audio=True)


@router.get(
'/{display_channel_id}/{quality}/ll-hls/{client_id}/secondary-audio/init',
summary = 'ライブ LL-HLS 初期セグメントデータ API (副音声)',
response_class = Response,
responses = {
status.HTTP_200_OK: {
'description': 'LL-HLS の初期セグメントデータ (m4s) 。',
'content': {'video/mp4': {}},
}
}
)
async def LiveLLHLSSecondaryAudioInitializationSegmentAPI(
livestream_client: LiveStreamClient = Depends(GetLiveStreamClient),
):
# クライアントから LL-HLS 初期セグメントデータのレスポンスを取得してそのまま返す
return await livestream_client.getInitializationSegment(secondary_audio=True)
3 changes: 0 additions & 3 deletions server/app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,6 @@ class LiveStreams(BaseModel):
Standby: dict[str, LiveStream]
Offline: dict[str, LiveStream]

class LiveStreamLLHLSClientID(BaseModel):
client_id: str

class ThirdpartyAuthURL(BaseModel):
authorization_url: str

Expand Down
36 changes: 0 additions & 36 deletions server/app/streams/LiveEncodingTask.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from app.config import Config
from app.constants import API_REQUEST_HEADERS, LIBRARY_PATH, LOGS_DIR, QUALITY, QUALITY_TYPES
from app.models.Channel import Channel
from app.streams.LiveHLSSegmenter import LiveHLSSegmenter
from app.streams.LivePSIDataArchiver import LivePSIDataArchiver
from app.utils import GetMirakurunAPIEndpointURL
from app.utils import Logging
Expand Down Expand Up @@ -404,17 +403,6 @@ async def run(self) -> None:
if not (self.livestream.getStatus()['status'] == 'Standby' and self.livestream.getStatus()['detail'] == 'エンコードタスクを起動しています…'):
self.livestream.setStatus('Standby', 'エンコードタスクを起動しています…')

# LL-HLS Segmenter に渡す今回のエンコードタスクの GOP 長 (H.264 と H.265 で異なる)
gop_length_second = self.GOP_LENGTH_SECOND_H264
if QUALITY[self.livestream.quality].is_hevc is True:
gop_length_second = self.GOP_LENGTH_SECOND_H265

# LL-HLS Segmenter を初期化
## iPhone Safari は mpegts.js でのストリーミングに対応していないため、フォールバックとして LL-HLS で配信する必要がある
## できるだけ早い段階で初期化しておかないと、初期化より前に iOS Safari からプレイリストにアクセスが来てしまい
## LL-HLS Segmenter is not running エラーが発生してしまう
self.livestream.segmenter = LiveHLSSegmenter(gop_length_second)

# チャンネル情報からサービス ID とネットワーク ID を取得する
channel = cast(Channel, await Channel.filter(display_channel_id=self.livestream.display_channel_id).first())

Expand Down Expand Up @@ -587,11 +575,6 @@ async def run(self) -> None:
self.livestream.psi_data_archiver.destroy()
self.livestream.psi_data_archiver = None

# LL-HLS Segmenter を破棄する
if self.livestream.segmenter is not None:
self.livestream.segmenter.destroy()
self.livestream.segmenter = None

# エンコードタスクを停止する
await session.close()
return
Expand Down Expand Up @@ -629,11 +612,6 @@ async def run(self) -> None:
self.livestream.psi_data_archiver.destroy()
self.livestream.psi_data_archiver = None

# LL-HLS Segmenter を破棄する
if self.livestream.segmenter is not None:
self.livestream.segmenter.destroy()
self.livestream.segmenter = None

# エンコードタスクを停止する
return

Expand Down Expand Up @@ -661,11 +639,6 @@ async def run(self) -> None:
self.livestream.psi_data_archiver.destroy()
self.livestream.psi_data_archiver = None

# LL-HLS Segmenter を破棄する
if self.livestream.segmenter is not None:
self.livestream.segmenter.destroy()
self.livestream.segmenter = None

# エンコードタスクを停止する
return

Expand Down Expand Up @@ -783,10 +756,6 @@ async def Writer():
## read() ではなく厳密な readexactly() を使わないとぴったり 188 bytes にならない場合がある
chunk = await cast(asyncio.StreamReader, encoder.stdout).readexactly(188)

# 受け取った TS パケットを LL-HLS Segmenter に送信する
if self.livestream.segmenter is not None:
self.livestream.segmenter.pushTSPacketData(chunk)

# 同時に chunk_buffer / chunk_written_at にアクセスするタスクが1つだけであることを保証する (排他ロック)
async with writer_lock:

Expand Down Expand Up @@ -1223,11 +1192,6 @@ async def Controller():
self.livestream.psi_data_archiver.destroy()
self.livestream.psi_data_archiver = None

# LL-HLS Segmenter を破棄する
if self.livestream.segmenter is not None:
self.livestream.segmenter.destroy()
self.livestream.segmenter = None

# エンコードタスクを再起動する(エンコーダーの再起動が必要な場合)
if self.livestream.getStatus()['status'] == 'Restart':

Expand Down
Loading

0 comments on commit 76d3dd4

Please sign in to comment.