KTVCentral/app/Http/Controllers/Api/RoomSongController.php

365 lines
14 KiB
PHP
Raw Normal View History

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\OrderSongRequest;
use App\Http\Requests\RoomSongRequest;
2025-08-21 18:20:37 +08:00
use App\Http\Requests\SyncOrderSongRequest;
use Illuminate\Http\Request;
2025-08-21 18:20:37 +08:00
use App\Services\MachineStatusForwarder;
use App\Models\Room;
2025-08-21 18:20:37 +08:00
use App\Models\SongLibraryCache;
use App\Models\OrderedSong;
use App\Models\RoomSession;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Carbon;
use App\Http\Responses\ApiResponse;
class RoomSongController extends Controller
{
/**
* @OA\Post(
* path="/api/room/order-song",
* summary="點歌",
* description="在指定包廂點一首歌曲",
* tags={"Room Control Song"},
* security={{"Authorization":{}}},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/OrderSongRequest")
* ),
* @OA\Response(
* response=200,
* description="成功",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(
* property="ordered_song",
* ref="#/components/schemas/OrderedSong"
* ),
* @OA\Property(property="next_song_name", type="string", example="XXXSSSS")
* )
* ),
* @OA\Response(
* response=422,
* description="驗證失敗",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="message", type="string", example="The given data was invalid."),
* @OA\Property(property="errors", type="object")
* )
* )
* )
*/
public function orderSong(OrderSongRequest $request)
{
// 取得對應的 RoomSession透過 api_token
$roomSession = $this->getRoomSession($request->api_token) ;
2025-08-21 18:20:37 +08:00
// 找這個 session 的最大 order_number下一首加 1
$lastOrder = OrderedSong::where('room_session_id', $roomSession->id)->max('order_number');
$orderNumber = $lastOrder ? $lastOrder + 1 : 1;
// 取得歌曲名稱
2025-08-21 18:20:37 +08:00
$song = SongLibraryCache::findOrFail($request->song_id);
// 建立 OrderedSong
$orderedSong = OrderedSong::create([
'room_session_id' => $roomSession->id,
'from_by' => $request->from_by,
2025-08-21 18:20:37 +08:00
'order_number' => $orderNumber,
'status' => $request->status,
'song_id' => $request->song_id,
2025-08-21 18:20:37 +08:00
'song_name' => $song->song_name,
'artist_name' => $song->str_artists_plus(),
'ordered_at' => now(),
]);
// 檢查這首歌在此 session 是否第一次點
$countInSession = OrderedSong::where('room_session_id', $roomSession->id)
->where('song_id', $request->song_id)
->count();
if ($countInSession === 1) { // 第一次點才加
$song->increment('song_counts');
}
2025-08-21 18:20:37 +08:00
$this->sys($roomSession,$orderedSong);
return ApiResponse::success([
'ordered_song' => $orderedSong,
'next_song_name' => $this->nextSongName($roomSession),
]);
}
2025-08-21 18:20:37 +08:00
private function sys($roomSession,$orderedSong)
{
$validated = [
'api_token' => $roomSession->api_token,
'order_number' => $orderedSong->order_number,
'from_by' => $orderedSong->from_by,
'song_id' => $orderedSong->song_id,
'song_name' => $orderedSong->song_name,
'artist_name' => $orderedSong->artist_name,
'status' => $orderedSong->status,
'ordered_at' => $orderedSong->ordered_at,
'started_at' => $orderedSong->started_at,
'finished_at' => $orderedSong->finished_at,
];
$response = (new MachineStatusForwarder(
$roomSession->room->branch->external_ip,
"/api/room/sync-order-song",
$validated,
$request->bearerToken()
2025-08-21 18:20:37 +08:00
))->forward();
}
public function syncOrderSong(SyncOrderSongRequest $request)
{
$roomSession = $this->getRoomSession($request->api_token) ;
// 建立或更新 OrderedSong
$orderedSong = OrderedSong::updateOrCreate(
[
'room_session_id' => $roomSession->id,
'order_number' => $request->order_number,
],
[
'from_by' => $request->from_by,
'song_id' => $request->song_id,
'song_name' => $request->song_name,
'artist_name' => $request->artist_name,
'status' => $request->status,
'ordered_at' => $request->ordered_at,
'started_at' => $request->started_at,
'finished_at' => $request->finished_at,
]
);
// 檢查這首歌在此 session 是否第一次點
if ($orderedSong->wasRecentlyCreated) {
$countInSession = OrderedSong::where('room_session_id', $roomSession->id)
->where('song_id', $request->song_id)->count();
if ($countInSession === 1) { // 第一次點才加
$song->increment('song_counts');
}
}
return ApiResponse::success([
'ordered_song' => $orderedSong
]);
}
/**
* @OA\Get(
* path="/api/room/ordered-songs",
* summary="取得包廂點歌列表",
* description="取得指定包廂的已播、正在播放、待播/插播歌曲列表",
* tags={"Room Control Song"},
* security={{"Authorization":{}}},
* @OA\Parameter(ref="#/components/parameters/ApiTokenQuery"),
* @OA\Response(
* response=200,
* description="成功取得點歌列表",
* @OA\JsonContent(
* type="object",
* @OA\Property(
* property="played",
* type="array",
* description="已播放 (Played, Skipped, NoFile)",
* @OA\Items(ref="#/components/schemas/OrderedSong")
* ),
* @OA\Property(
* property="playing",
* type="array",
* description="正在播放中 (Playing)",
* @OA\Items(ref="#/components/schemas/OrderedSong")
* ),
* @OA\Property(
* property="not_played",
* type="array",
* description="未播放 (NotPlayed, InsertPlayback)",
* @OA\Items(ref="#/components/schemas/OrderedSong")
* )
* )
* ),
* @OA\Response(
* response=422,
* description="驗證失敗",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="message", type="string", example="The given data was invalid."),
* @OA\Property(property="errors", type="object")
* )
* )
* )
*/
public function listOrderedSongs(RoomSongRequest $request)
{
$roomSession = $this->getRoomSession($request->api_token) ;
// 已結束 (finished + canceled)
$played = OrderedSong::where('room_session_id', $roomSession->id)
->whereIn('status', ['Played', 'Skipped', 'NoFile'])
->orderByDesc('finished_at')
->get();
$playing = OrderedSong::where('room_session_id', $roomSession->id)
->whereIn('status', ['Playing'])
->get();
// 正在播 + 插播 + 待播
$not_played = OrderedSong::where('room_session_id', $roomSession->id)
->whereIn('status', ['Playing', 'InsertPlayback', 'NotPlayed'])
->orderByRaw("FIELD(status, 'Playing', 'InsertPlayback', 'NotPlayed')") // playing > InsertPlayback > NotPlayed
->orderByRaw("CASE
WHEN status = 'InsertPlayback' THEN ordered_at END DESC") // InsertPlayback 越後排越前
->orderByRaw("CASE
WHEN status = 'NotPlayed' THEN ordered_at END ASC") // NotPlayed 越後排越後
->get();
return ApiResponse::success([
'played' => $played,
'playing' => $playing,
'not_played' => $not_played,
]);
}
/**
* @OA\Post(
* path="/api/room/current-song",
* summary="取得目前播放中的歌曲",
* description="回傳當前房間正在播放的歌曲資訊 (包含部分 song 欄位)",
* tags={"Room Control Song"},
* security={{"Authorization":{}}},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/RoomSongRequest")
* ),
* @OA\Response(
* response=200,
* description="成功回傳目前播放的歌曲",
* @OA\JsonContent(
* type="object",
* @OA\Property(
* property="success",
* type="boolean",
* example=true
* ),
* @OA\Property(
* property="data",
* type="object",
* @OA\Property(
* property="current",
* ref="#/components/schemas/OrderedSongWithPartialSong"
* )
* )
* )
* )
* )
*/
public function currentSong(RoomSongRequest $request)
{
$roomSession = $this->getRoomSession($request->api_token) ;
$current = OrderedSong::where('room_session_id', $roomSession->id)
->where('status', 'Playing')
->withPartialSong()
->first();
return ApiResponse::success([
'current' => $current ,
]);
}
/**
* @OA\Post(
* path="/api/room/next-song",
* summary="播放下一首歌曲",
* description="將目前播放的歌標記為結束,並將下一首設為播放中,回傳下首與下下首資訊。",
* tags={"Room Control Song"},
* security={{"Authorization":{}}},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/RoomSongRequest")
* ),
* @OA\Response(
* response=200,
* description="成功回傳歌曲資訊",
* @OA\JsonContent(
* type="object",
* @OA\Property(
* property="current",
* type="object",
* nullable=true,
* ref="#/components/schemas/OrderedSongWithPartialSong"
* ),
* @OA\Property(
* property="next",
* type="object",
* nullable=true,
* ref="#/components/schemas/OrderedSongWithPartialSong"
* )
* )
* ),
* @OA\Response(
* response=401,
* description="未授權或 Token 錯誤"
* ),
* @OA\Response(
* response=404,
* description="找不到房間或歌曲"
* )
* )
*/
public function nextSong(RoomSongRequest $request)
{
$roomSession = $this->getRoomSession($request->api_token) ;
// 找目前正在播
$current = OrderedSong::where('room_session_id', $roomSession->id)
->where('status', 'Playing')
->first();
if ($current) {
// 把當前設為 finished並記錄結束時間
$current->update([
'status' => 'Played',
'finished_at' => now(),
]);
2025-08-21 18:20:37 +08:00
$this->sys($roomSession,$current);
}
// 撈出候播清單(下首 + 下下首)
$queue = OrderedSong::where('room_session_id', $roomSession->id)
->whereIn('status', ['InsertPlayback', 'NotPlayed'])
->withPartialSong()
->orderByRaw("FIELD(status, 'InsertPlayback', 'NotPlayed')") // InsertPlayback > NotPlayed
->orderByRaw("CASE WHEN status = 'InsertPlayback' THEN ordered_at END DESC") // InsertPlayback 後插播的先播
->orderByRaw("CASE WHEN status = 'NotPlayed' THEN ordered_at END ASC") // NotPlayed 先點的先播
->limit(2)
->get();
$current =$queue->get(0);
if ($current) {
$current->update([
'status' => 'Playing',
'started_at' => now(),
]);
2025-08-21 18:20:37 +08:00
$this->sys($roomSession,$current);
}
return ApiResponse::success([
'current' => $current,// 下首
'next' => $queue->get(1) ?? null,// 下下首
]);
}
private function nextSongName(RoomSession $roomSession)
{
// 找下首
$next = OrderedSong::where('room_session_id', $roomSession->id)
->whereIn('status', ['InsertPlayback', 'NotPlayed'])
->with('song')
->orderByRaw("FIELD(status, 'InsertPlayback', 'NotPlayed')")
->orderByRaw("CASE WHEN status = 'InsertPlayback' THEN ordered_at END DESC")
->orderByRaw("CASE WHEN status = 'NotPlayed' THEN ordered_at END ASC")
->first();
return $next?->song?->name;
}
/**
* 取得對應的 RoomSession
*/
private function getRoomSession($api_token): RoomSession
{
return RoomSession::where('api_token', $api_token)
->whereIn('status', ['active', 'maintain'])
->firstOrFail();
}
}