Add video engagement tracking and heatmap
This commit is contained in:
@@ -4,11 +4,14 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Episode;
|
||||
use App\Models\VideoEngagement;
|
||||
use App\Models\Watched;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
class StreamApiController extends Controller
|
||||
{
|
||||
@@ -70,4 +73,71 @@ class StreamApiController extends Controller
|
||||
|
||||
return response()->json(['watched' => true], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track engagement segments that the authenticated user has watched.
|
||||
* Called periodically client-side while the video is playing.
|
||||
* Segments are 10-second chunks; segment 0 (0-10s) is excluded.
|
||||
*/
|
||||
public function trackEngagement(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'episode_id' => 'required|integer|exists:episodes,id',
|
||||
'segments' => 'required|array',
|
||||
'segments.*' => 'integer|min:1',
|
||||
]);
|
||||
|
||||
$user = Auth::user();
|
||||
if (! $user) {
|
||||
return response()->json(['tracked' => 0], 401);
|
||||
}
|
||||
|
||||
$rateLimitKey = 'engagement:'.$user->id;
|
||||
if (RateLimiter::tooManyAttempts($rateLimitKey, 10)) {
|
||||
$seconds = RateLimiter::availableIn($rateLimitKey);
|
||||
|
||||
return response()->json([
|
||||
'tracked' => 0,
|
||||
'message' => 'Rate limit exceeded. Try again in '.$seconds.' seconds.',
|
||||
], 429);
|
||||
}
|
||||
RateLimiter::hit($rateLimitKey, 60);
|
||||
|
||||
$episodeId = $request->input('episode_id');
|
||||
$segments = $request->input('segments');
|
||||
$tracked = 0;
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
// upsert: insert if not exists, otherwise ignore (unique constraint prevents dupes)
|
||||
VideoEngagement::firstOrCreate([
|
||||
'episode_id' => $episodeId,
|
||||
'user_id' => $user->id,
|
||||
'segment' => $segment,
|
||||
]);
|
||||
$tracked++;
|
||||
}
|
||||
|
||||
return response()->json(['tracked' => $tracked], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get engagement heatmap data for an episode.
|
||||
* Returns watch counts per segment, aggregated across all users.
|
||||
*/
|
||||
public function getEngagement(int $episodeId): JsonResponse
|
||||
{
|
||||
$episode = Episode::findOrFail($episodeId);
|
||||
|
||||
$data = cache()->remember(
|
||||
"engagement:{$episodeId}",
|
||||
600, // 10-minute cache
|
||||
fn () => VideoEngagement::where('episode_id', $episodeId)
|
||||
->select('segment', DB::raw('count(*) as watch_count'))
|
||||
->groupBy('segment')
|
||||
->pluck('watch_count', 'segment')
|
||||
->toArray()
|
||||
);
|
||||
|
||||
return response()->json($data, 200);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user