Skip to content

Instantly share code, notes, and snippets.

@cheikhsimsol
Created December 30, 2024 19:37
Show Gist options
  • Save cheikhsimsol/12183f73b250bc09952ae52b5f36f53e to your computer and use it in GitHub Desktop.
Save cheikhsimsol/12183f73b250bc09952ae52b5f36f53e to your computer and use it in GitHub Desktop.
Full video stream example
package main
import (
"fmt"
"io"
"log"
"net/http"
"strconv"
"strings"
)
const url = "https://download.samplelib.com/mp4/sample-5s.mp4"
type VideoStreamer interface {
Seek(key string, start, end int) ([]byte, error)
}
func VideoStreamHandler(streamer VideoStreamer, videoKey string, totalSize int) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rangeHeader := r.Header.Get("Range")
var start, end int
if rangeHeader == "" {
// Default to first 1MB
start = 0
end = 1024*1024 - 1
} else {
// Parse the Range header: "bytes=start-end"
rangeParts := strings.TrimPrefix(rangeHeader, "bytes=")
rangeValues := strings.Split(rangeParts, "-")
var err error
// Get start byte
start, err = strconv.Atoi(rangeValues[0])
if err != nil {
http.Error(w, "Invalid start byte", http.StatusBadRequest)
return
}
// Get end byte or set to default
if len(rangeValues) > 1 && rangeValues[1] != "" {
end, err = strconv.Atoi(rangeValues[1])
if err != nil {
http.Error(w, "Invalid end byte", http.StatusBadRequest)
return
}
} else {
end = start + 1024*1024 - 1 // Default 1MB chunk
}
}
// Ensure end is within the total video size
if end >= totalSize {
end = totalSize - 1
}
// Fetch the video data
videoData, err := streamer.Seek(videoKey, start, end)
if err != nil {
http.Error(w, fmt.Sprintf("Error retrieving video: %v", err), http.StatusInternalServerError)
return
}
// Set headers and serve the video chunk
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, totalSize))
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(videoData)))
w.Header().Set("Content-Type", "video/mp4")
w.WriteHeader(http.StatusPartialContent)
_, err = w.Write(videoData)
if err != nil {
http.Error(w, "Error streaming video", http.StatusInternalServerError)
}
}
}
func DownloadBytes(url string) ([]byte, error) {
// Send a GET request to the URL
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Read the response body
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return data, nil
}
func main() {
data, err := DownloadBytes(url)
if err != nil {
fmt.Println("Error downloading:", err)
return
}
log.Println("length of data:", len(data))
streamer := &MockVideoStreamer{
Store: map[string][]byte{"video-key": data},
}
http.HandleFunc("/video", VideoStreamHandler(streamer, "video-key", len(data)))
http.ListenAndServe(":8080", nil)
}
// MockVideoStreamer is a simple mock implementation of the VideoStreamer interface
type MockVideoStreamer struct {
Store map[string][]byte
}
// Seek retrieves a video chunk from the store based on the key and byte range
func (m *MockVideoStreamer) Seek(key string, start, end int) ([]byte, error) {
// Retrieve the video data from the store using the key
videoData, exists := m.Store[key]
if !exists {
return nil, fmt.Errorf("video not found")
}
// Ensure the range is within the bounds of the video data
if start < 0 || start >= len(videoData) {
return nil, fmt.Errorf("start byte %d out of range", start)
}
if end < start || end >= len(videoData) {
end = len(videoData) - 1 // Adjust end to the last byte if it's out of bounds
}
// Return the requested slice of video data
return videoData[start : end+1], nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment