diff --git a/Dockerfile b/Dockerfile
index 8c58ef4..63db6ec 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,56 +1,49 @@
-# ===========================
-# Stage 1: Frontend Build
-# ===========================
-FROM node:20-alpine AS frontend-builder
+# Stage 1: Build Image (Frontend)
+FROM node:18-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend-react/package*.json ./
-RUN npm ci
+RUN npm install
COPY frontend-react/ .
RUN npm run build
-# ===========================
-# Stage 2: Backend Build
-# ===========================
-FROM golang:1.22-alpine AS backend-builder
+# Stage 2: Build Image (Backend)
+FROM golang:1.23-alpine AS backend-builder
WORKDIR /app/backend
-
-# Install necessary build tools for CGO (SQLite)
+# Install build dependencies
RUN apk add --no-cache gcc musl-dev
COPY backend/go.mod backend/go.sum ./
RUN go mod download
COPY backend/ .
-# Build Linux binary
-RUN CGO_ENABLED=1 GOOS=linux go build -o server ./cmd/server/main.go
+# Build static binary for Linux amd64
+RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server cmd/server/main.go
-# ===========================
-# Stage 3: Runtime
-# ===========================
-FROM python:3.11-alpine
+# Stage 3: Final Image
+FROM alpine:latest
WORKDIR /app
-# Install Runtime Dependencies
-# - ffmpeg: for yt-dlp media handling
-# - yt-dlp: via pip
-# - ca-certificates: for HTTPS
-RUN apk add --no-cache ffmpeg ca-certificates && \
- pip install --no-cache-dir yt-dlp
+# Install runtime dependencies (sqlite)
+RUN apk add --no-cache sqlite ca-certificates tzdata
-# Create non-root user
-RUN addgroup -S streamflow && adduser -S streamflow -G streamflow
+# Copy backend binary
+COPY --from=backend-builder /app/backend/server .
-# Copy Frontend Build
-COPY --from=frontend-builder /app/frontend/dist /app/frontend/dist
+# Copy frontend build to the expected static directory
+# The backend expects ../frontend-react/dist relative to itself, or we configure it.
+# Let's align with the standard deployment structure: /app/server and /app/dist
+COPY --from=frontend-builder /app/frontend/dist ./dist
-# Copy Backend Binary
-COPY --from=backend-builder /app/backend/server /app/server
+# Create data directory
+RUN mkdir -p data
-# Setup Permissions
-RUN chown -R streamflow:streamflow /app
-
-USER streamflow
+# Environment variables
+ENV PORT=8000
+ENV DATABASE_URL=sqlite:///app/data/streamflow.db
+ENV GIN_MODE=release
+# Expose port
EXPOSE 8000
-CMD ["/app/server"]
+# Start server
+CMD ["./server"]
diff --git a/docker-compose.yml b/docker-compose.yml
index 99e192d..d338f55 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,18 +1,19 @@
version: '3.8'
services:
- app:
+ streamflow:
build: .
- image: streamflow:v2
+ image: streamflow:latest
+ container_name: streamflow
+ platform: linux/amd64
ports:
- - "8000:8000"
+ - "3478:8000"
environment:
- - DATABASE_URL=sqlite:///./data/streamflow.db
+ - DATABASE_URL=sqlite:///app/data/streamflow.db
- TMDB_API_KEY=${TMDB_API_KEY}
volumes:
- - streamflow_data:/app/data
- - streamflow_cache:/app/cache
- restart: unless-stopped
+ - ./data:/app/data
+ restart: always
volumes:
streamflow_data:
diff --git a/frontend-react/src/hooks/useWatchMovie.ts b/frontend-react/src/hooks/useWatchMovie.ts
index d906a13..7d20fee 100644
--- a/frontend-react/src/hooks/useWatchMovie.ts
+++ b/frontend-react/src/hooks/useWatchMovie.ts
@@ -70,18 +70,73 @@ export const useWatchMovie = (slug: string | undefined, episode: string | undefi
hls.loadSource(source.stream_url);
hls.attachMedia(videoRef.current);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
- videoRef.current?.play();
+ videoRef.current?.play().catch(() => { });
});
return () => {
hls.destroy();
};
} else if (videoRef.current.canPlayType('application/vnd.apple.mpegurl')) {
videoRef.current.src = source.stream_url;
- videoRef.current.play();
+ videoRef.current.play().catch(() => { });
}
}
}, [source]);
+ // Wake Lock Logic (Prevent Screen Sleep)
+ useEffect(() => {
+ const video = videoRef.current;
+ let wakeLock: any = null;
+
+ const requestWakeLock = async () => {
+ try {
+ if ('wakeLock' in navigator) {
+ wakeLock = await (navigator as any).wakeLock.request('screen');
+ // console.log('Wake Lock active');
+ }
+ } catch (err) {
+ console.warn('Wake Lock failed:', err);
+ }
+ };
+
+ const releaseWakeLock = async () => {
+ if (wakeLock) {
+ try {
+ await wakeLock.release();
+ wakeLock = null;
+ // console.log('Wake Lock released');
+ } catch (err) {
+ console.warn('Wake Lock release failed:', err);
+ }
+ }
+ };
+
+ if (video) {
+ const onPlay = () => requestWakeLock();
+ const onPause = () => releaseWakeLock();
+ const onEnded = () => releaseWakeLock();
+
+ video.addEventListener('play', onPlay);
+ video.addEventListener('pause', onPause);
+ video.addEventListener('ended', onEnded);
+
+ // Re-acquire on visibility change if playing
+ const onVisibilityChange = () => {
+ if (document.visibilityState === 'visible' && !video.paused) {
+ requestWakeLock();
+ }
+ };
+ document.addEventListener('visibilitychange', onVisibilityChange);
+
+ return () => {
+ video.removeEventListener('play', onPlay);
+ video.removeEventListener('pause', onPause);
+ video.removeEventListener('ended', onEnded);
+ document.removeEventListener('visibilitychange', onVisibilityChange);
+ releaseWakeLock();
+ };
+ }
+ }, [source]); // Re-run when source changes (new video loaded)
+
return {
movie,
source,
diff --git a/frontend-react/src/themes/apple/WatchPage.tsx b/frontend-react/src/themes/apple/WatchPage.tsx
index 844311c..f2df061 100644
--- a/frontend-react/src/themes/apple/WatchPage.tsx
+++ b/frontend-react/src/themes/apple/WatchPage.tsx
@@ -71,7 +71,8 @@ export const WatchPage = ({ slug, episode }: { slug: string, episode: string })
{/* Content Section - Scrolls over the bottom of the player if sticky, or just below */}
-
+ {/* Content Section - Scrolls over the bottom of the player if sticky, or just below */}
+
{/* Movie Info */}
@@ -93,7 +94,7 @@ export const WatchPage = ({ slug, episode }: { slug: string, episode: string })
{episodes.length} available
-
+
{(expanded ? episodes : episodes.slice(0, 20)).map((ep) => (