Created
December 30, 2024 19:37
-
-
Save cheikhsimsol/12183f73b250bc09952ae52b5f36f53e to your computer and use it in GitHub Desktop.
Full video stream example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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