Fix: play hls in safari

pull/67/head
zijiren233 1 year ago
parent 038845b15a
commit 05f186bd6c

@ -35,7 +35,7 @@ require (
github.com/zencoder/go-dash/v3 v3.0.3
github.com/zijiren233/gencontainer v0.0.0-20240108151314-9632e4fe47e7
github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb
github.com/zijiren233/livelib v0.3.0
github.com/zijiren233/livelib v0.3.1
github.com/zijiren233/stream v0.5.1
github.com/zijiren233/yaml-comment v0.2.1
go.etcd.io/etcd/client/v3 v3.5.11

@ -362,6 +362,8 @@ github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb h1:0DyOxf/
github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb/go.mod h1:6TCzjDiQ8+5gWZiwsC3pnA5M0vUy2jV2Y7ciHJh729g=
github.com/zijiren233/livelib v0.3.0 h1:RLfx6vSUr7wfK6F0VY0aFOBDcT/ljvjQUTOlHEcUlUQ=
github.com/zijiren233/livelib v0.3.0/go.mod h1:2wrAAqNIdMZjQrdbO7ERQfqK4VS5fzgUj2xXwrJ8/uo=
github.com/zijiren233/livelib v0.3.1 h1:vNGQFeVyk1qrXTO/lqyRs0oC6cLzMD6yo2Jdym3XNpI=
github.com/zijiren233/livelib v0.3.1/go.mod h1:2wrAAqNIdMZjQrdbO7ERQfqK4VS5fzgUj2xXwrJ8/uo=
github.com/zijiren233/stream v0.5.1 h1:9SUwM/fpET6frtBRT5WZBHnan0Hyzkezk/P8N78cgZQ=
github.com/zijiren233/stream v0.5.1/go.mod h1:iIrOm3qgIepQFmptD/HDY+YzamSSzQOtPjpVcK7FCOw=
github.com/zijiren233/yaml-comment v0.2.1 h1:/ymMfauuR6zPme+c59FvGNmvxmjOS+BRZSU9YEM82g4=

@ -27,6 +27,7 @@ function Help() {
echo "-v set build version (default: dev)"
echo "-S set source dir (default: ../)"
echo "-m set build mode (default: pie)"
echo "-M disable build micro"
echo "-l set ldflags (default: -s -w --extldflags \"-static -fpic\")"
echo "-p set platform (default: host platform, support: all, linux, darwin, windows)"
echo "-P set disable trim path (default: disable)"
@ -77,7 +78,7 @@ function Init() {
}
function ParseArgs() {
while getopts "hCsS:v:w:m:l:p:Pd:T:tm" arg; do
while getopts "hCsS:v:w:m:l:p:Pd:T:tmM" arg; do
case $arg in
h)
Help
@ -116,6 +117,9 @@ function ParseArgs() {
m)
GH_PROXY="https://mirror.ghproxy.com/"
;;
M)
DISABLE_MICRO="true"
;;
# ----
# dep
s)
@ -898,7 +902,6 @@ function InitLinuxAmd64CGODeps() {
function Build() {
platform="$1"
target_name="$2"
disable_micro="$3"
GOOS=${platform%/*}
GOARCH=${platform#*/}
@ -946,10 +949,17 @@ function Build() {
GOOS=$GOOS \
GOARCH=$GOARCH"
if [ "$disable_micro" ]; then
if [ "$DISABLE_MICRO" ]; then
echo "building $GOOS/$GOARCH"
InitCGODeps "$GOOS" "$GOARCH"
eval "$BUILD_ENV CC=\"$CC\" CXX=\"$CXX\" go build $BUILD_FLAGS -o \"$TARGET_FILE$EXT\" \"$SOURCH_DIR\""
eval "$BUILD_ENV CC=\"$CC\" CXX=\"$CXX\" \
GO386=sse2 \
GOARM=6 \
GOAMD64=v1 \
GOMIPS=hardfloat GOMIPS64=hardfloat \
GOPPC64=power8 \
GOWASM= \
go build $BUILD_FLAGS -o \"$TARGET_FILE$EXT\" \"$SOURCH_DIR\""
if [ $? -ne 0 ]; then
echo "build $GOOS/$GOARCH failed"
exit 1
@ -1182,7 +1192,7 @@ function Build() {
function AutoBuild() {
if [ ! "$1" ]; then
echo "build host platform: $GOHOSTOS/$GOHOSTARCH"
Build "$GOHOSTOS/$GOHOSTARCH" "$BIN_NAME" "disable_micro"
Build "$GOHOSTOS/$GOHOSTARCH" "$BIN_NAME"
else
for platform in $1; do
if [ "$platform" == "all" ]; then

@ -186,11 +186,18 @@ func initMovie(movie *gin.RouterGroup, needAuthMovie *gin.RouterGroup) {
movie.GET("/proxy/:roomId/:movieId", ProxyMovie)
{
live := needAuthMovie.Group("/live")
live := movie.Group("/live")
needAuthLive := needAuthMovie.Group("/live")
live.POST("/publishKey", NewPublishKey)
needAuthLive.POST("/publishKey", NewPublishKey)
live.GET("/*movieId", JoinLive)
// needAuthLive.GET("/join/:movieId", JoinLive)
needAuthLive.GET("/flv/:movieId", JoinFlvLive)
needAuthLive.GET("/hls/list/:movieId", JoinHlsLive)
live.GET("/hls/data/:roomId/:movieId/:dataId", ServeHlsLive)
}
}

@ -12,7 +12,7 @@ import (
"math/rand"
"net/http"
"net/url"
"path"
"path/filepath"
"strconv"
"strings"
@ -85,11 +85,14 @@ func genCurrent(ctx context.Context, user *op.User, room *op.Room, current *op.C
return parse2VendorMovie(ctx, user, room, &current.Movie)
}
if current.Movie.Base.RtmpSource || current.Movie.Base.Live && current.Movie.Base.Proxy {
t := current.Movie.Base.Type
if t != "flv" && t != "m3u8" {
t = "m3u8"
switch current.Movie.Base.Type {
case "m3u8":
current.Movie.Base.Url = fmt.Sprintf("/api/movie/live/hls/list/%s.m3u8", current.Movie.ID)
case "flv":
current.Movie.Base.Url = fmt.Sprintf("/api/movie/live/flv/%s.flv", current.Movie.ID)
default:
return errors.New("not support live movie type")
}
current.Movie.Base.Url = fmt.Sprintf("/api/movie/live/%s.%s", current.Movie.ID, t)
current.Movie.Base.Headers = nil
} else if current.Movie.Base.Proxy {
current.Movie.Base.Url = fmt.Sprintf("/api/movie/proxy/%s/%s", current.Movie.RoomID, current.Movie.ID)
@ -558,14 +561,10 @@ func (e FormatErrNotSupportFileType) Error() string {
}
func JoinLive(ctx *gin.Context) {
ctx.Header("Cache-Control", "no-store")
room := ctx.MustGet("room").(*op.RoomEntry).Value()
// user := ctx.MustGet("user").(*op.UserEntry)
movieId := strings.Trim(ctx.Param("movieId"), "/")
fileExt := path.Ext(movieId)
splitedMovieId := strings.Split(movieId, "/")
channelName := strings.TrimSuffix(splitedMovieId[0], fileExt)
m, err := room.GetMovieByID(channelName)
m, err := room.GetMovieByID(movieId)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
@ -582,38 +581,144 @@ func JoinLive(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
if channel == nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorStringResp("channel is nil"))
return
}
switch fileExt {
case ".flv":
ctx.Header("Cache-Control", "no-store")
joinType := ctx.DefaultQuery("type", "auto")
if joinType == "auto" {
joinType = m.Movie.Base.Type
}
switch joinType {
case "flv":
w := httpflv.NewHttpFLVWriter(ctx.Writer)
defer w.Close()
channel.AddPlayer(w)
w.SendPacket()
case ".m3u8":
ctx.Header("Cache-Control", "no-store")
err = channel.AddPlayer(w)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
_ = w.SendPacket()
case "m3u8":
b, err := channel.GenM3U8File(func(tsName string) (tsPath string) {
ext := "ts"
if settings.TsDisguisedAsPng.Get() {
ext = "png"
}
return fmt.Sprintf("/api/movie/live/%s/%s.%s", channelName, tsName, ext)
return fmt.Sprintf("/api/movie/live/hls/data/%s/%s/%s.%s", room.ID, movieId, tsName, ext)
})
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
ctx.Data(http.StatusOK, hls.M3U8ContentType, b)
default:
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp(fmt.Sprintf("not support join type: %s", joinType)))
return
}
}
func JoinFlvLive(ctx *gin.Context) {
ctx.Header("Cache-Control", "no-store")
room := ctx.MustGet("room").(*op.RoomEntry).Value()
movieId := strings.TrimSuffix(strings.Trim(ctx.Param("movieId"), "/"), ".flv")
m, err := room.GetMovieByID(movieId)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
if m.Movie.Base.RtmpSource && !conf.Conf.Server.Rtmp.Enable {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled"))
return
} else if m.Movie.Base.Live && !settings.LiveProxy.Get() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy is not enabled"))
return
}
channel, err := m.Channel()
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
w := httpflv.NewHttpFLVWriter(ctx.Writer)
defer w.Close()
err = channel.AddPlayer(w)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
_ = w.SendPacket()
}
func JoinHlsLive(ctx *gin.Context) {
ctx.Header("Cache-Control", "no-store")
room := ctx.MustGet("room").(*op.RoomEntry).Value()
movieId := strings.TrimSuffix(strings.Trim(ctx.Param("movieId"), "/"), ".m3u8")
m, err := room.GetMovieByID(movieId)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
if m.Movie.Base.RtmpSource && !conf.Conf.Server.Rtmp.Enable {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled"))
return
} else if m.Movie.Base.Live && !settings.LiveProxy.Get() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy is not enabled"))
return
}
channel, err := m.Channel()
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
b, err := channel.GenM3U8File(func(tsName string) (tsPath string) {
ext := "ts"
if settings.TsDisguisedAsPng.Get() {
ext = "png"
}
return fmt.Sprintf("/api/movie/live/hls/data/%s/%s/%s.%s", room.ID, movieId, tsName, ext)
})
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
ctx.Data(http.StatusOK, hls.M3U8ContentType, b)
}
func ServeHlsLive(ctx *gin.Context) {
ctx.Header("Cache-Control", "no-store")
roomId := ctx.Param("roomId")
roomE, err := op.LoadOrInitRoomByID(roomId)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
room := roomE.Value()
movieId := ctx.Param("movieId")
m, err := room.GetMovieByID(movieId)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
if m.Movie.Base.RtmpSource && !conf.Conf.Server.Rtmp.Enable {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled"))
return
} else if m.Movie.Base.Live && !settings.LiveProxy.Get() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy is not enabled"))
return
}
channel, err := m.Channel()
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
dataId := ctx.Param("dataId")
switch fileExt := filepath.Ext(dataId); fileExt {
case ".ts":
if settings.TsDisguisedAsPng.Get() {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt)))
return
}
b, err := channel.GetTsFile(splitedMovieId[1])
b, err := channel.GetTsFile(strings.TrimSuffix(dataId, fileExt))
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
@ -625,7 +730,7 @@ func JoinLive(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt)))
return
}
b, err := channel.GetTsFile(splitedMovieId[1])
b, err := channel.GetTsFile(strings.TrimSuffix(dataId, fileExt))
if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return

@ -180,7 +180,12 @@ func NewAuthRoomToken(user *op.User, room *op.Room) (string, error) {
}
func AuthRoomMiddleware(ctx *gin.Context) {
userE, roomE, err := AuthRoom(ctx.GetHeader("Authorization"))
token, err := GetAuthorizationTokenFromContext(ctx)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
userE, roomE, err := AuthRoom(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
@ -212,7 +217,12 @@ func AuthRoomMiddleware(ctx *gin.Context) {
}
func AuthUserMiddleware(ctx *gin.Context) {
user, err := AuthUser(ctx.GetHeader("Authorization"))
token, err := GetAuthorizationTokenFromContext(ctx)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
user, err := AuthUser(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
@ -231,7 +241,12 @@ func AuthUserMiddleware(ctx *gin.Context) {
}
func AuthAdminMiddleware(ctx *gin.Context) {
user, err := AuthUser(ctx.GetHeader("Authorization"))
token, err := GetAuthorizationTokenFromContext(ctx)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
user, err := AuthUser(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
@ -246,7 +261,12 @@ func AuthAdminMiddleware(ctx *gin.Context) {
}
func AuthRootMiddleware(ctx *gin.Context) {
user, err := AuthUser(ctx.GetHeader("Authorization"))
token, err := GetAuthorizationTokenFromContext(ctx)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
user, err := AuthUser(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
@ -259,3 +279,15 @@ func AuthRootMiddleware(ctx *gin.Context) {
ctx.Set("user", user)
ctx.Next()
}
func GetAuthorizationTokenFromContext(ctx *gin.Context) (string, error) {
Authorization := ctx.GetHeader("Authorization")
if Authorization != "" {
return Authorization, nil
}
Authorization = ctx.Query("token")
if Authorization != "" {
return Authorization, nil
}
return "", errors.New("token is empty")
}

Loading…
Cancel
Save