2025-08-20 15:26:05 +08:00
|
|
|
|
<?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;
|
2025-08-20 15:26:05 +08:00
|
|
|
|
use Illuminate\Http\Request;
|
2025-08-21 18:20:37 +08:00
|
|
|
|
use App\Services\MachineStatusForwarder;
|
2025-08-20 15:26:05 +08:00
|
|
|
|
use App\Models\Room;
|
2025-08-21 18:20:37 +08:00
|
|
|
|
use App\Models\SongLibraryCache;
|
2025-08-20 15:26:05 +08:00
|
|
|
|
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-20 15:26:05 +08:00
|
|
|
|
// 取得歌曲名稱
|
2025-08-21 18:20:37 +08:00
|
|
|
|
$song = SongLibraryCache::findOrFail($request->song_id);
|
2025-08-20 15:26:05 +08:00
|
|
|
|
|
|
|
|
|
// 建立 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,
|
2025-08-20 15:26:05 +08:00
|
|
|
|
'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(),
|
2025-08-20 15:26:05 +08:00
|
|
|
|
'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);
|
2025-08-20 15:26:05 +08:00
|
|
|
|
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
|
|
|
|
|
))->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
|
|
|
|
|
]);
|
|
|
|
|
}
|
2025-08-20 15:26:05 +08:00
|
|
|
|
/**
|
|
|
|
|
* @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);
|
2025-08-20 15:26:05 +08:00
|
|
|
|
}
|
|
|
|
|
// 撈出候播清單(下首 + 下下首)
|
|
|
|
|
$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);
|
2025-08-20 15:26:05 +08:00
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|