When a new user logs into the app their account will be rejected by default, unless they are an admin user or automatic approval is enabled. Admin users exclusively control who is permitted to log in, changing a users Approved flag will stop them from navigating around if they're active otherwise if they're not allowed it will prevent them from logging in and they will get a "Waiting for Approval" messaging.
New: You can now configure automatic user approval using the AUTO_APPROVE_USERS environment variable. See the User Approval System documentation for details.
Unfortunately not all browsers support HEVC properly with HDR, so if you're playing a 4k designated video it will require you to cast it or use a browser that supports HEVC proper. There's a list on the web for HEVC with HDR supported browsers but I usually go to Microsoft Edge for my 4k video playback or any videos really. Seems to preload faster overall for videos in Edge.
Note: It is recommended to self-host the app and all related services on the same machine to simplify setup.
To implement the NextJS-Stream app, follow these steps:
-
Visit the following GitHub repository: https://github.com/Apezdr/nextjs-stream
-
Clone the repository to your local machine:
git clone https://github.com/Apezdr/nextjs-stream.git -
Navigate to the cloned directory:
cd nextjs-stream -
Install the required dependencies:
npm install -
Configure the environment variables: Create a
.env.localfile in the root directory and add the following variables:# Database Configuration MONGODB_URI=mongodb://dbUser:your_password@your_ip:your_port/?authMechanism=DEFAULT MONGODB_DB=Media MONGODB_DB_USERS=Users # Better Auth Configuration BETTER_AUTH_URL=https://your-domain.com BETTER_AUTH_SECRET=your_better_auth_secret GOOGLE_CLIENT_ID=your_google_client_id GOOGLE_CLIENT_SECRET=your_google_client_secret DISCORD_CLIENT_ID=your_discord_client_id DISCORD_CLIENT_SECRET=your_discord_client_secret # TMDB Server Configuration (Optional) # Dedicated TMDB server URL for external media integration # Falls back to NODE_SERVER_URL if not set TMDB_NODE_SERVER_URL=https://subdomain.your-domain.com/node/tmdb # Optional # Chromecast Configuration (Optional) CHROMECAST_RECEIVER_ID=your_chromecast_receiver_id # Optional # Base URL Configuration NEXT_PUBLIC_BASE_URL=https://cinema.your-domain.com # Webhook Configuration WEBHOOK_ID=322fb39e4591514d2b8c1697sbc72c9c WEBHOOK_ID_2=521ebe9e6211514d2b8c1697sbc72c98 # Radarr Configuration (Optional) # Only needed if you are using Radarr integration RADARR_ICAL_LINK=https://your-radarr-server.com/radarr/feed/v3/calendar/Radarr.ics?unmonitored=true&apikey=your_api_key&PastDays=1880&FutureDays=380 # Optional RADARR_URL=your_radarr_url # Optional RADARR_API_KEY=your_radarr_api_key # Optional # Sonarr Configuration (Optional) # Only needed if you are using Sonarr integration SONARR_URL=your_sonarr_url # Optional SONARR_API_KEY=your_sonarr_api_key # Optional # Tdarr Configuration (Optional) # Only needed if you are using Tdarr integration TDARR_URL=your_tdarr_url # Optional TDARR_API_KEY=your_tdarr_api_key # Optional # SABnzbd Configuration (Optional) # Only needed if you are using SABnzbd integration SABNZBD_URL=http://192.168.1.2:8080/sabnzbd # Example URL, replace with your actual URL # Optional SABNZBD_API_KEY=your_sabnzbd_api_key # Optional # Public URLs and Paths ORGANIZR_URL=http://localhost:3000 FILE_SERVER_URL=https://subdomain.your-domain.com FILE_SERVER_PREFIX_PATH= # Optional prefix path for file server ADMIN_USER_EMAILS=email1@xxxx.com,email2@zzzz.com # Site Information (Optional) # Use these variables to customize the site title and description NEXT_PUBLIC_SITE_TITLE=Cinema Sanctuary # Optional NEXT_PUBLIC_SITE_DESCRIPTION=Sharing media content with friends and family. # Optional # Sync URL, uses the node server NODE_SERVER_URL=http://localhost:3000 # User Approval Settings (Optional) # Set to 'true' to automatically approve new users, 'false' to require manual admin approval (default) AUTO_APPROVE_USERS=false # Optional
Replace the placeholder values with your actual credentials and API keys. Anywhere you see
cinema.your-domain.comorsubdomain.your-domain.comyou don't have to use a subdomain but it would generally be the approach on the same host.The
NODE_SERVER_INTERNAL_URLenvironment variable provides an optional optimization for server-to-server communication on local area networks (LANs). When set, internal backend operations (sync, blurhash generation, subtitle processing, TMDB proxy) use this internal endpoint instead of the publicNODE_SERVER_URL, reducing DNS lookups and routing overhead.Key points:
- Optional: If
NODE_SERVER_INTERNAL_URLis not set, the system falls back to the publicNODE_SERVER_URL. - Server-to-server only: This endpoint is used exclusively for backend-to-backend communication. Client-facing URLs (such as clip URLs produced in
src/utils/auth_utils.js) continue using the publicNODE_SERVER_URL/syncEndpointto ensure external reachability and HTTPS security. - Implementation: See
src/utils/config.jsfor the configuration logic. TheinternalEndpointproperty defaults tosyncEndpointwhen not explicitly set.
Environment variables:
For multiple servers, use numbered variants corresponding to
NODE_SERVER_URL,NODE_SERVER_URL_2, etc.:NODE_SERVER_INTERNAL_URL- Internal endpoint for the default serverNODE_SERVER_INTERNAL_URL_2- Internal endpoint for server 2NODE_SERVER_INTERNAL_URL_3- Internal endpoint for server 3- And so on...
Example configurations:
Docker Compose:
environment: NODE_SERVER_URL: 'https://personalserver.example.com/nodejs' NODE_SERVER_INTERNAL_URL: 'http://192.168.1.39:3000'
.env.local(Development):NODE_SERVER_URL=https://cinema.your-domain.com NODE_SERVER_INTERNAL_URL=http://localhost:3000
Fallback behavior:
If
NODE_SERVER_INTERNAL_URLis not set,server.internalEndpointwill default tosyncEndpoint(the value ofNODE_SERVER_URL). This ensures seamless operation whether you use internal endpoints or not. Reference the implementation insrc/utils/config.jswhereinternalEndpointfalls back tosyncEndpoint.Verification checklist:
To test that your internal endpoint is working:
- Set both
NODE_SERVER_URL(public) andNODE_SERVER_INTERNAL_URL(internal) in your environment - Restart your container:
docker-compose restart nextjs-stream
- Check server logs for debug entries showing both endpoints:
Look for log entries like
docker logs -f nextjs-stream
[CONFIG] Loaded server config for default: syncEndpoint=... internalEndpoint=... - Trigger a sync operation or API call to a TMDB proxy route to observe internal network traffic
- Verify that server-to-server calls use the internal endpoint while client-facing URLs use the public endpoint
To set up an admin user (until roles are implemented proper) you can set up admin users through the environment variable
ADMIN_USER_EMAILSthere can be one or multiple, seperate multiple with a comma.Single:
email1@xxxx.comMultiple:
email1@xxxx.com,email2@zzzz.comIf your Next.js app and Node server are running on different subdomains of the same parent domain, you can enable cross-domain authentication to allow session cookies to work across both domains.
Enable this feature when:
- Next.js app:
cinema.example.com - Node server:
node.example.com - Both are subdomains of the same parent domain (
.example.com)
Don't use this feature when:
- Both services are on the same domain (
cinema.example.com/node) - Services are on completely different domains (
cinema.comandnodeserver.net)
Add the following environment variable to enable cross-domain authentication:
# Cross-Domain Authentication (Optional) # Set this to your parent domain with a leading dot AUTH_COOKIE_DOMAIN=.example.com
Examples:
- For
app.mysite.comandapi.mysite.com:AUTH_COOKIE_DOMAIN=.mysite.com
- Only use if you control all subdomains of the parent domain
- Cookies will be accessible to ALL subdomains of the parent domain
- For completely different domains, use webhook authentication instead
- Requires HTTPS for secure cookie transmission
Authentication not working across domains:
-
Check domain configuration:
- Verify
AUTH_COOKIE_DOMAINstarts with a dot (.example.com) - Ensure both domains are subdomains of the specified parent domain
- Verify
-
Verify HTTPS:
- Cross-domain secure cookies require HTTPS on both domains
- Check browser console for cookie security errors
-
Test configuration:
- Check server logs for authentication configuration messages
- Look for
[AUTH CONFIG]log entries showing your domain setup
Browser console errors:
- If you see cookie errors in browser console, verify the cookie domain matches your actual domain structure
- Ensure
sameSite: 'lax'is compatible with your domain setup
Alternative for different domains:
- If domains are completely different, use the existing webhook authentication system instead
- Webhook auth works across any domain configuration
If you're using the auto sync feature you must set up the
WEBHOOK_IDor if using multiple serversWEBHOOK_ID_2etc.; with a randomized string to act as the key. This also is used for node server health checks.Endpoint:
POST->/api/admin/syncParams:
X-Webhook-ID: Use one of your ID's set up underWEBHOOK_ID,Content-Type:application/json - Optional: If
-
Configure next.config.js: Update your
next.config.jsfile with the following configurations:Disclaimer: It's recommended to only update the remotePatterns to match the server hosting the files for your media server.
const nextConfig = { reactStrictMode: true, experimental: { reactCompiler: true, esmExternals: false, }, pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], eslint: { dirs: ['app', 'components', 'layouts', 'scripts'], ignoreDuringBuilds: false, }, images: { minimumCacheTTL: 604800, remotePatterns: [ { protocol: 'https', hostname: 'subdomain.your-domain.com', }, { protocol: 'https', hostname: 'image.tmdb.org', }, { protocol: 'https', hostname: 'm.media-amazon.com', }, { protocol: 'https', hostname: 'iconape.com', }, { protocol: 'https', hostname: 'www.freeiconspng.com', }, ], }, } module.exports = nextConfig
Replace 'subdomain.your-domain.com' with your actual server hostname (think in the context of a file host).
-
Run the development server:
npm run dev -
Open your browser and visit
http://localhost:3000to see the app running. -
Explore the code in the repository to understand the implementation details and customize as needed for your project.
Remember to keep your .env.local file secure and never commit it to version control.
This application includes a comprehensive watchlist system with support for both internal library media and external TMDB-only content. Users can create custom playlists, share them with others, and manage their viewing preferences.
- Personal Watchlists: Add movies and TV shows from your library or external TMDB content
- Custom Playlists: Create and organize multiple themed playlists
- Playlist Sharing: Share playlists with other users with configurable permissions
- External Media Support: Add movies/shows not in your library via TMDB integration
- Dual Search System: Comprehensive search across both internal database and TMDB
- Bulk Operations: Manage multiple items at once
- Smart Filtering: Advanced search and filter capabilities
The application supports a dedicated TMDB server configuration for enhanced external media integration:
TMDB_API_KEY: Your TMDB API key (required for external media features)TMDB_NODE_SERVER_URL: Optional dedicated TMDB server URL- Falls back to
NODE_SERVER_URLif not specified - Useful for load balancing or dedicated TMDB processing
- Falls back to
Basic Setup (using main server):
TMDB_API_KEY=your_tmdb_api_key
NODE_SERVER_URL=https://your-server.com/nodeDedicated TMDB Server:
TMDB_API_KEY=your_tmdb_api_key
NODE_SERVER_URL=https://your-server.com/node
TMDB_NODE_SERVER_URL=https://tmdb-server.your-domain.com/nodeThe watchlist system provides comprehensive REST API endpoints:
GET /api/authenticated/watchlist?action=list- Get user's watchlistPOST /api/authenticated/watchlist?action=add- Add item to watchlistPOST /api/authenticated/watchlist?action=toggle- Toggle item in watchlistDELETE /api/authenticated/watchlist?action=remove- Remove item from watchlistGET /api/authenticated/watchlist?action=status- Check if item is in watchlist
GET /api/authenticated/watchlist?action=playlists- Get user's playlistsPOST /api/authenticated/watchlist?action=create-playlist- Create new playlistPUT /api/authenticated/watchlist?action=update-playlist- Update playlistDELETE /api/authenticated/watchlist?action=delete-playlist- Delete playlistPOST /api/authenticated/watchlist?action=share-playlist- Share playlist with users
POST /api/authenticated/watchlist?action=bulk-update- Bulk update itemsDELETE /api/authenticated/watchlist?action=bulk-remove- Bulk remove itemsPOST /api/authenticated/watchlist?action=move-items- Move items between playlists
Administrators can monitor TMDB server status through the admin dashboard:
- Connection Status: Real-time TMDB server connectivity monitoring
- Configuration Validation: Verify API keys and server URLs
- Health Metrics: Response times and service availability
- Error Diagnostics: Detailed error reporting and troubleshooting
TMDB Server Not Configured:
- Ensure
TMDB_API_KEYis set in your environment - Verify
TMDB_NODE_SERVER_URLorNODE_SERVER_URLis configured - Check the admin dashboard for configuration status
Connection Failures:
- Verify server URLs are accessible
- Check firewall and network settings
- Review server logs for detailed error messages
API Key Issues:
- Ensure your TMDB API key is valid and active
- Check API key permissions and rate limits
- Verify the key is correctly set in environment variables
Use the admin dashboard to test your TMDB configuration:
- Navigate to the admin panel
- Check the "TMDB Server Status" section
- Use the refresh button to test connectivity
- Review any error messages for troubleshooting guidance
When adding external media (not in your library):
- Search: Use the TMDB search functionality to find content
- Add to Watchlist: External items are marked with TMDB metadata
- Playlist Organization: External and internal media can be mixed in playlists
- Sharing: Shared playlists include both internal and external content
External media items include rich metadata from TMDB:
- High-quality posters and backdrops
- Detailed descriptions and cast information
- Release dates and ratings
- Genre classifications
The watchlist system features a comprehensive dual search approach that provides the best of both worlds:
- Database Search: Fast partial matching against your internal media library
- TMDB Search: Comprehensive search against The Movie Database
- Smart Deduplication: Automatically filters out TMDB results already in your library
- Visual Grouping: Clear categorization of search results by source
- In Your Watchlist: Items already added to your current playlist
- Available in Media Server: Content from your library that can be added immediately
- Add from TMDB: External content not in your library but available via TMDB
- Comprehensive Coverage: Find content whether it's in your library or not
- Performance Optimized: Database searches are lightning fast
- No Duplicates: Smart filtering prevents showing the same content twice
- Rich Metadata: All results include posters, descriptions, and release information
The watchlist system uses an intelligent, deterministic approach to display content that ensures maximum performance and reliability:
- Database-First: Primary lookup using internal
mediaIdin flat collections (fastest) - TMDB Fallback: If
mediaIdfails, lookup usingtmdbIdviametadata.idin flat collections - API Fallback: If both database lookups fail, fetch display data from TMDB API
- Performance: Database lookups are extremely fast compared to API calls
- Resilience: Multiple fallback mechanisms ensure content is always displayed
- Automatic Detection: Content automatically appears as "available" when added to your library
- Backward Compatibility: Works with existing watchlist items regardless of how they were added
- Playlist Loading: When you view a playlist, each item goes through the hydration process
- Smart Detection: The system automatically determines if content is in your library or external
- Seamless Experience: Internal content shows with "Available now!" and direct links
- External Display: TMDB-only content shows with rich metadata but no direct links
- Available Content: Green checkmark with "Available now!" and clickable links
- External Content: Yellow "EXTERNAL" badge with TMDB metadata for browsing
- Rich Metadata: All content shows posters, descriptions, ratings, and genre information
This approach ensures your watchlist always reflects the current state of your media library without requiring manual updates or complex synchronization processes.
While it is possible to host this app on Vercel, please be aware that the image optimization costs may prevent practical usage at scale. If you choose to deploy on Vercel, it's recommended to use the unoptimized flag for next/image to avoid these costs at the expense of placeholder images being unavailable.
Ref: https://nextjs.org/docs/app/api-reference/components/image#unoptimized
To do this, add the following to your next.config.js:
module.exports = {
images: {
unoptimized: true,
},
}