Add play button to video thumbnails and implement video streaming endpoint
This commit is contained in:
		@@ -58,6 +58,25 @@
 | 
				
			|||||||
			opacity: 0.7;
 | 
								opacity: 0.7;
 | 
				
			||||||
			z-index: 1;
 | 
								z-index: 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							.play-button {
 | 
				
			||||||
 | 
								position: absolute;
 | 
				
			||||||
 | 
								top: 50%;
 | 
				
			||||||
 | 
								left: 50%;
 | 
				
			||||||
 | 
								transform: translate(-50%, -50%);
 | 
				
			||||||
 | 
								width: 80px; /* Adjust size as needed */
 | 
				
			||||||
 | 
								height: 80px; /* Adjust size as needed */
 | 
				
			||||||
 | 
								background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M8 5v14l11-7z'/%3E%3C/svg%3E");
 | 
				
			||||||
 | 
								background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
 | 
				
			||||||
 | 
								background-repeat: no-repeat;
 | 
				
			||||||
 | 
								background-position: center;
 | 
				
			||||||
 | 
								background-size: 50%; /* Adjust icon size within button */
 | 
				
			||||||
 | 
								border-radius: 50%; /* Circular button */
 | 
				
			||||||
 | 
								cursor: pointer;
 | 
				
			||||||
 | 
								z-index: 3; /* Ensure it's above the video thumbnails */
 | 
				
			||||||
 | 
								border: 2px solid white;
 | 
				
			||||||
 | 
								box-shadow: 0 0 15px rgba(0,0,0,0.7);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	</style>
 | 
						</style>
 | 
				
			||||||
	<script>
 | 
						<script>
 | 
				
			||||||
		function hideLoader() {
 | 
							function hideLoader() {
 | 
				
			||||||
@@ -91,6 +110,7 @@
 | 
				
			|||||||
		<img id="prev-thumb" class="video-thumbnail side-video" alt="Previous Video">
 | 
							<img id="prev-thumb" class="video-thumbnail side-video" alt="Previous Video">
 | 
				
			||||||
		<img id="current-thumb" class="video-thumbnail current-video" alt="Current Video">
 | 
							<img id="current-thumb" class="video-thumbnail current-video" alt="Current Video">
 | 
				
			||||||
		<img id="next-thumb" class="video-thumbnail side-video" alt="Next Video">
 | 
							<img id="next-thumb" class="video-thumbnail side-video" alt="Next Video">
 | 
				
			||||||
 | 
							<div class="play-button" id="play-button"></div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										105
									
								
								webview.go
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								webview.go
									
									
									
									
									
								
							@@ -1,10 +1,15 @@
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"gitea.seeseepuff.be/seeseemelk/mysqlite"
 | 
						"gitea.seeseepuff.be/seeseemelk/mysqlite"
 | 
				
			||||||
	"github.com/donseba/go-htmx"
 | 
						"github.com/donseba/go-htmx"
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type App struct {
 | 
					type App struct {
 | 
				
			||||||
@@ -23,6 +28,7 @@ func serveWebview(db *mysqlite.Db) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	router := gin.Default()
 | 
						router := gin.Default()
 | 
				
			||||||
	router.StaticFile("/", "static/index.html")
 | 
						router.StaticFile("/", "static/index.html")
 | 
				
			||||||
 | 
						router.GET("/stream/:id", app.serveStream)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	api := router.Group("/api")
 | 
						api := router.Group("/api")
 | 
				
			||||||
	api.GET("/homepage", app.homePage)
 | 
						api.GET("/homepage", app.homePage)
 | 
				
			||||||
@@ -47,6 +53,7 @@ func serveWebview(db *mysqlite.Db) {
 | 
				
			|||||||
type VideoModel struct {
 | 
					type VideoModel struct {
 | 
				
			||||||
	ID        int    `json:"id"`
 | 
						ID        int    `json:"id"`
 | 
				
			||||||
	Thumbnail string `json:"thumbnail"`
 | 
						Thumbnail string `json:"thumbnail"`
 | 
				
			||||||
 | 
						URL       string `json:"url"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type HomePageModel struct {
 | 
					type HomePageModel struct {
 | 
				
			||||||
@@ -63,6 +70,7 @@ func (a *App) homePage(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	model.CurrentVideo.Thumbnail = "https://imgproxy.fourthwall.com/GHX1MSukwJHMQeiqcJLZL906v2--re7qQbQIlHgy5jY/rs:fill:607:342:0/q:90/sm:1/enc/NDJiMWM5YzU3ODJh/MzgzZXFIti10srar/EYcgeYXIe6MXrQge/VBPZVk3b6DCE53dq/VGponLkc45F9743G/4la9S4IjbcNxs4lF/WnLXCMzFhaSUaZnL/Og4de_FSYxQOtzCC/0pfkgH1g4FJfajde/KrZfes95EWXzg9sP/kvEwcqro2EM.webp"
 | 
						model.CurrentVideo.Thumbnail = "https://imgproxy.fourthwall.com/GHX1MSukwJHMQeiqcJLZL906v2--re7qQbQIlHgy5jY/rs:fill:607:342:0/q:90/sm:1/enc/NDJiMWM5YzU3ODJh/MzgzZXFIti10srar/EYcgeYXIe6MXrQge/VBPZVk3b6DCE53dq/VGponLkc45F9743G/4la9S4IjbcNxs4lF/WnLXCMzFhaSUaZnL/Og4de_FSYxQOtzCC/0pfkgH1g4FJfajde/KrZfes95EWXzg9sP/kvEwcqro2EM.webp"
 | 
				
			||||||
 | 
						model.CurrentVideo.URL = fmt.Sprintf("/stream/%d", model.CurrentVideo.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.JSON(200, model)
 | 
						c.JSON(200, model)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -178,52 +186,57 @@ func (a *App) homePage(c *gin.Context) {
 | 
				
			|||||||
//	}
 | 
					//	}
 | 
				
			||||||
//	a.renderTemplate(w, "video", vm)
 | 
					//	a.renderTemplate(w, "video", vm)
 | 
				
			||||||
//}
 | 
					//}
 | 
				
			||||||
//
 | 
					
 | 
				
			||||||
//func (a *App) serveStream(w http.ResponseWriter, r *http.Request) {
 | 
					func (a *App) serveStream(c *gin.Context) {
 | 
				
			||||||
//	// Parse the video ID from the URL
 | 
						// Parse the video ID from the URL
 | 
				
			||||||
//	pathParts := strings.Split(r.URL.Path, "/")
 | 
						idStr := c.Param("id")
 | 
				
			||||||
//	if len(pathParts) < 3 {
 | 
						videoID, err := strconv.Atoi(idStr)
 | 
				
			||||||
//		http.Error(w, "Invalid video ID", http.StatusBadRequest)
 | 
						if err != nil {
 | 
				
			||||||
//		return
 | 
							log.Printf("Failed to parse video ID: %v", err)
 | 
				
			||||||
//	}
 | 
							c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid video ID"})
 | 
				
			||||||
//
 | 
							return
 | 
				
			||||||
//	videoIDStr := pathParts[2]
 | 
						}
 | 
				
			||||||
//	videoID, err := strconv.Atoi(videoIDStr)
 | 
					
 | 
				
			||||||
//	if err != nil {
 | 
						// Get video metadata from database
 | 
				
			||||||
//		http.Error(w, "Invalid video ID", http.StatusBadRequest)
 | 
						var year, episode int
 | 
				
			||||||
//		return
 | 
						err = a.db.Query(`SELECT year, episode FROM videos WHERE id = ?`).Bind(videoID).ScanSingle(&year, &episode)
 | 
				
			||||||
//	}
 | 
						if err != nil {
 | 
				
			||||||
//
 | 
							log.Printf("Failed to find video: %v", err)
 | 
				
			||||||
//	// Get video metadata from database
 | 
							c.JSON(http.StatusNotFound, gin.H{"error": "Video not found"})
 | 
				
			||||||
//	var year, episode int
 | 
							return
 | 
				
			||||||
//	err = a.db.Query(`SELECT year, episode FROM videos WHERE id = ?`).Bind(videoID).ScanSingle(&year, &episode)
 | 
						}
 | 
				
			||||||
//	if err != nil {
 | 
					
 | 
				
			||||||
//		http.Error(w, "Video not found", http.StatusNotFound)
 | 
						// Determine the video file path
 | 
				
			||||||
//		return
 | 
						downloadDir := os.Getenv("VIVAPLUS_DESTINATION")
 | 
				
			||||||
//	}
 | 
						if downloadDir == "" {
 | 
				
			||||||
//
 | 
							downloadDir = "./downloads"
 | 
				
			||||||
//	// Determine the video file path
 | 
						}
 | 
				
			||||||
//	downloadDir := os.Getenv("VIVAPLUS_DESTINATION")
 | 
					
 | 
				
			||||||
//	if downloadDir == "" {
 | 
						seasonDir := filepath.Join(downloadDir, fmt.Sprintf("Season %d", year))
 | 
				
			||||||
//		downloadDir = "./downloads"
 | 
						videoFilename := fmt.Sprintf("S%dE%03d.mp4", year, episode)
 | 
				
			||||||
//	}
 | 
						videoPath := filepath.Join(seasonDir, videoFilename)
 | 
				
			||||||
//
 | 
					
 | 
				
			||||||
//	seasonDir := filepath.Join(downloadDir, fmt.Sprintf("Season %d", year))
 | 
						// Check if the file exists
 | 
				
			||||||
//	videoFilename := fmt.Sprintf("S%dE%03d.mp4", year, episode)
 | 
						log.Printf("Streaming video: %s", videoPath)
 | 
				
			||||||
//	videoPath := filepath.Join(seasonDir, videoFilename)
 | 
						_, err = os.Stat(videoPath)
 | 
				
			||||||
//
 | 
						if os.IsNotExist(err) {
 | 
				
			||||||
//	// Check if the file exists
 | 
							log.Printf("Video file not found: %s", videoPath)
 | 
				
			||||||
//	log.Printf("Streaming video: %s", videoPath)
 | 
							c.JSON(http.StatusNotFound, gin.H{"error": "Video file not found"})
 | 
				
			||||||
//	_, err = os.Stat(videoPath)
 | 
							return
 | 
				
			||||||
//	if os.IsNotExist(err) {
 | 
						}
 | 
				
			||||||
//		http.Error(w, "Video file not found", http.StatusNotFound)
 | 
					
 | 
				
			||||||
//		return
 | 
						// Serve the file
 | 
				
			||||||
//	}
 | 
						w := c.Writer
 | 
				
			||||||
//
 | 
						r := c.Request
 | 
				
			||||||
//	// Serve the file
 | 
						w.Header().Set("Content-Type", "video/mp4")
 | 
				
			||||||
//	http.ServeFile(w, r, videoPath)
 | 
						w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", videoFilename))
 | 
				
			||||||
//}
 | 
						w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
 | 
				
			||||||
//
 | 
						w.Header().Set("Pragma", "no-cache")
 | 
				
			||||||
 | 
						w.Header().Set("Expires", "0")
 | 
				
			||||||
 | 
						log.Printf("Serving video file: %s", videoPath)
 | 
				
			||||||
 | 
						http.ServeFile(w, r, videoPath)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//func (a *App) handleMarkWatched(w http.ResponseWriter, r *http.Request) {
 | 
					//func (a *App) handleMarkWatched(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
//	// Only handle POST requests
 | 
					//	// Only handle POST requests
 | 
				
			||||||
//	if r.Method != http.MethodPost {
 | 
					//	if r.Method != http.MethodPost {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user