up
This commit is contained in:
parent
7f332aa88b
commit
b1866a2cc5
@ -228,6 +228,7 @@ func RoutesProtected(r *mux.Router, bd *gorm.DB) {
|
|||||||
r.HandleFunc("/api/paths/{id:[0-9]+}/media", renders.PathMedia(bd)).Methods("GET")
|
r.HandleFunc("/api/paths/{id:[0-9]+}/media", renders.PathMedia(bd)).Methods("GET")
|
||||||
r.HandleFunc("/stream/{partID:[0-9]+}", renders.Stream(bd)).Methods("GET")
|
r.HandleFunc("/stream/{partID:[0-9]+}", renders.Stream(bd)).Methods("GET")
|
||||||
r.HandleFunc("/media/{partID:[0-9]+}", renders.MediaDetail(bd)).Methods("GET")
|
r.HandleFunc("/media/{partID:[0-9]+}", renders.MediaDetail(bd)).Methods("GET")
|
||||||
|
r.HandleFunc("/hls/{partID:[0-9]+}/{file}", renders.HLSStream(bd)).Methods("GET")
|
||||||
|
|
||||||
//API Scan folder
|
//API Scan folder
|
||||||
|
|
||||||
|
|||||||
@ -985,7 +985,7 @@ type mediaDetailView struct {
|
|||||||
Summary string
|
Summary string
|
||||||
DurationFmt string
|
DurationFmt string
|
||||||
ThumbURL string
|
ThumbURL string
|
||||||
StreamURL string
|
HLSURL string // ajouté
|
||||||
}
|
}
|
||||||
|
|
||||||
// MediaDetail affiche la page détail + player
|
// MediaDetail affiche la page détail + player
|
||||||
@ -1024,7 +1024,7 @@ func MediaDetail(db *gorm.DB) http.HandlerFunc {
|
|||||||
Summary: item.Summary,
|
Summary: item.Summary,
|
||||||
DurationFmt: strconv.FormatInt(m, 10) + ":" + fmt.Sprintf("%02d", s),
|
DurationFmt: strconv.FormatInt(m, 10) + ":" + fmt.Sprintf("%02d", s),
|
||||||
ThumbURL: item.UserThumbURL,
|
ThumbURL: item.UserThumbURL,
|
||||||
StreamURL: "/stream/" + strconv.FormatInt(item.MediaPartID, 10),
|
HLSURL: fmt.Sprintf("/hls/%d/index.m3u8", partID),
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -1098,6 +1098,49 @@ func Stream(db *gorm.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renders/media.go (ajoutez cette fonction)
|
||||||
|
func HLSStream(db *gorm.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 1) identifier le média
|
||||||
|
partID, _ := strconv.ParseInt(mux.Vars(r)["partID"], 10, 64)
|
||||||
|
var part models.MediaPart
|
||||||
|
if err := db.First(&part, partID).Error; err != nil {
|
||||||
|
http.Error(w, "Média introuvable", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) dossier temporaire / cache
|
||||||
|
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_%d", partID))
|
||||||
|
os.MkdirAll(tmpDir, 0755)
|
||||||
|
playlist := filepath.Join(tmpDir, "index.m3u8")
|
||||||
|
|
||||||
|
// 3) si besoin, (re)générer l’HLS via ffmpeg
|
||||||
|
if _, err := os.Stat(playlist); os.IsNotExist(err) {
|
||||||
|
// ffmpeg -i input -c:v copy -c:a copy -f hls -hls_time 4 -hls_list_size 0 tmp/index.m3u8
|
||||||
|
cmd := exec.CommandContext(r.Context(),
|
||||||
|
"ffmpeg",
|
||||||
|
"-i", part.File,
|
||||||
|
"-c:v", "copy", "-c:a", "copy",
|
||||||
|
"-f", "hls",
|
||||||
|
"-hls_time", "4",
|
||||||
|
"-hls_list_size", "0",
|
||||||
|
"-hls_segment_filename", filepath.Join(tmpDir, "seg%d.ts"),
|
||||||
|
playlist,
|
||||||
|
)
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
log.Println("ffmpeg HLS error:", err, string(out))
|
||||||
|
http.Error(w, "Erreur de transcodage", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) servir le dossier HLS en statique
|
||||||
|
http.StripPrefix(
|
||||||
|
fmt.Sprintf("/hls/%d/", partID),
|
||||||
|
http.FileServer(http.Dir(tmpDir)),
|
||||||
|
).ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,39 @@
|
|||||||
<div class="detail p-4">
|
<div class="detail p-4">
|
||||||
<h1 class="text-2xl font-bold mb-4">{{.item.Title}}</h1>
|
<h1 class="text-2xl font-bold mb-4">{{.item.Title}}</h1>
|
||||||
{{with .item.Summary}}
|
{{with .item.Summary}}<p class="mb-4">{{.}}</p>{{end}}
|
||||||
<p class="mb-4">{{.}}</p>
|
{{with .item.DurationFmt}}<small class="text-gray-500">{{.}}</small>{{end}}
|
||||||
{{end}}
|
|
||||||
{{with .item.DurationFmt}}
|
|
||||||
<small class="text-gray-500">{{.}}</small>
|
|
||||||
{{end}}
|
|
||||||
<img src="{{.item.ThumbURL}}" alt="{{.item.Title}}" class="cover mb-4 rounded shadow" />
|
<img src="{{.item.ThumbURL}}" alt="{{.item.Title}}" class="cover mb-4 rounded shadow" />
|
||||||
<video controls autoplay width="100%" class="rounded shadow">
|
|
||||||
<source src="{{.item.StreamURL}}" type="video/mp4">
|
<!-- 1) Le bouton Play -->
|
||||||
Votre navigateur ne supporte pas la vidéo.
|
<button
|
||||||
</video>
|
id="play-btn"
|
||||||
|
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||||
|
>▶ Play</button>
|
||||||
|
|
||||||
|
<!-- 2) Container vide pour le player -->
|
||||||
|
<div id="player-container" class="mt-4"></div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/hls.js@1.4.0"></script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('play-btn').addEventListener('click', function(){
|
||||||
|
const url = "{{.item.HLSURL}}";
|
||||||
|
const container = document.getElementById('player-container');
|
||||||
|
// injecte la balise video
|
||||||
|
container.innerHTML = '<video id="video-player" controls autoplay width="100%" class="rounded shadow"></video>';
|
||||||
|
const video = document.getElementById('video-player');
|
||||||
|
|
||||||
|
// si hls.js est supporté, on l’utilise
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
const hls = new Hls();
|
||||||
|
hls.loadSource(url);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
} else {
|
||||||
|
// sinon on pose directement la source (Chrome & Safari gèrent nativement HLS)
|
||||||
|
video.src = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache le bouton
|
||||||
|
this.style.display = 'none';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user