Compare commits

...

10 Commits

Author SHA1 Message Date
Rogee
b5961c12f7 feat: support playback speed
Some checks failed
Build TGExporter / Build (push) Failing after 38s
2025-01-07 22:49:19 +08:00
Rogee
2673c9fdde feat: add cron message notify 2025-01-06 19:24:11 +08:00
Rogee
019297d261 fix: ios player 2025-01-04 01:02:35 +08:00
Rogee
9df2663b6e feat: add charge codes 2024-12-28 16:38:46 +08:00
Rogee
cee14cb3fd fix: play segments 2024-12-28 16:05:25 +08:00
Rogee
dae2941168 fix: issues with default player 2024-12-28 15:17:19 +08:00
Rogee
464937cf84 fix: video radio 2024-12-28 12:22:56 +08:00
Rogee
549b18daf0 feat: add codes 2024-12-21 15:13:38 +08:00
Rogee
9ef5707ca2 feat: set worker duration to 1hour 2024-12-19 22:57:40 +08:00
Rogee
24553e9875 feat: update by query 2024-12-19 22:56:54 +08:00
26 changed files with 243 additions and 153 deletions

View File

@@ -12,6 +12,5 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -153,7 +153,7 @@ func (d *DiscoverMedias) getFileMD5(filePath string) (string, error) {
} }
// extractAudio extract audio from video // extractAudio extract audio from video
func (d *DiscoverMedias) extractAudio(video string, output string) error { func (d *DiscoverMedias) extractAudio(video, output string) error {
args := []string{ args := []string{
"-i", video, "-i", video,
"-vn", "-vn",
@@ -303,7 +303,7 @@ func (d *DiscoverMedias) runCleanup(to string) {
} }
// getSnapshot get the snapshot of target seconds of a video // getSnapshot get the snapshot of target seconds of a video
func (d *DiscoverMedias) ffmpegVideoToPoster(video string, output string) error { func (d *DiscoverMedias) ffmpegVideoToPoster(video, output string) error {
args := []string{ args := []string{
"-y", // 添加 -y 参数强制覆盖 "-y", // 添加 -y 参数强制覆盖
"-i", video, "-i", video,

View File

@@ -21,6 +21,5 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -21,6 +21,5 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -21,7 +21,6 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := container.Container.Provide(func( if err := container.Container.Provide(func(
userSvc *users.Service, userSvc *users.Service,
) (*Create, error) { ) (*Create, error) {
@@ -35,7 +34,6 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := container.Container.Provide(func( if err := container.Container.Provide(func(
userSvc *users.Service, userSvc *users.Service,
) (*Expire, error) { ) (*Expire, error) {
@@ -49,6 +47,5 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -8,7 +8,7 @@ import (
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
hashids "github.com/speps/go-hashids/v2" "github.com/speps/go-hashids/v2"
) )
// @provider // @provider
@@ -82,7 +82,8 @@ func (c *Controller) MediaIndex(ctx fiber.Ctx) error {
if claim == nil { if claim == nil {
return errorx.RequestUnAuthorized return errorx.RequestUnAuthorized
} }
// c.Locals(consts.CtxKeyJwt, token)
token := fiber.Locals[string](ctx, consts.CtxKeyJwt)
model, err := c.svc.GetMediaByHash(ctx.Context(), claim.TenantID, hash) model, err := c.svc.GetMediaByHash(ctx.Context(), claim.TenantID, hash)
if err != nil { if err != nil {
log.WithField("action", "medias.MediaIndex").WithError(err).Error("GetMediaByHash") log.WithField("action", "medias.MediaIndex").WithError(err).Error("GetMediaByHash")
@@ -95,12 +96,13 @@ func (c *Controller) MediaIndex(ctx fiber.Ctx) error {
return err return err
} }
playlist, err := c.svc.GetM3U8(ctx.Context(), claim.TenantID, mediaType, model.Hash, bought) playlist, err := c.svc.GetM3U8(ctx.Context(), claim.TenantID, mediaType, model.Hash, bought, token)
if err != nil { if err != nil {
log.WithField("action", "medias.MediaIndex").WithError(err).Error("GetMediaPlaylist") log.WithField("action", "medias.MediaIndex").WithError(err).Error("GetMediaPlaylist")
return err return err
} }
ctx.Set("Content-Type", "application/vnd.apple.mpegurl")
return ctx.SendString(playlist.String()) return ctx.SendString(playlist.String())
} }
@@ -131,6 +133,7 @@ func (c *Controller) MediaSegment(ctx fiber.Ctx) error {
} }
filepath := c.svc.GetSegmentPath(ctx.Context(), mediaType, model.TenantID, model.Hash, segments[0]) filepath := c.svc.GetSegmentPath(ctx.Context(), mediaType, model.TenantID, model.Hash, segments[0])
ctx.Set("Content-Type", "video/mp2t")
return ctx.SendFile(filepath) return ctx.SendFile(filepath)
} }

View File

@@ -8,7 +8,7 @@ import (
"git.ipao.vip/rogeecn/atom/container" "git.ipao.vip/rogeecn/atom/container"
"git.ipao.vip/rogeecn/atom/contracts" "git.ipao.vip/rogeecn/atom/contracts"
"git.ipao.vip/rogeecn/atom/utils/opt" "git.ipao.vip/rogeecn/atom/utils/opt"
hashids "github.com/speps/go-hashids/v2" "github.com/speps/go-hashids/v2"
) )
func Provide(opts ...opt.Option) error { func Provide(opts ...opt.Option) error {
@@ -24,7 +24,6 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := container.Container.Provide(func( if err := container.Container.Provide(func(
controller *Controller, controller *Controller,
) (contracts.HttpRoute, error) { ) (contracts.HttpRoute, error) {
@@ -38,7 +37,6 @@ func Provide(opts ...opt.Option) error {
}, atom.GroupRoutes); err != nil { }, atom.GroupRoutes); err != nil {
return err return err
} }
if err := container.Container.Provide(func( if err := container.Container.Provide(func(
db *sql.DB, db *sql.DB,
hashIds *hashids.HashID, hashIds *hashids.HashID,
@@ -56,6 +54,5 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -18,6 +18,7 @@ import (
"backend/providers/storage" "backend/providers/storage"
. "github.com/go-jet/jet/v2/postgres" . "github.com/go-jet/jet/v2/postgres"
"github.com/go-jet/jet/v2/qrm"
"github.com/grafov/m3u8" "github.com/grafov/m3u8"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/samber/lo" "github.com/samber/lo"
@@ -163,7 +164,7 @@ func (svc *Service) List(ctx context.Context, tenantId, userId int64, filter *Li
} }
// GetUserBoughtMedias // GetUserBoughtMedias
func (svc *Service) GetUserBoughtMedias(ctx context.Context, tenant int64, userID int64) ([]int64, error) { func (svc *Service) GetUserBoughtMedias(ctx context.Context, tenant, userID int64) ([]int64, error) {
log := svc.log.WithField("method", "GetUserBoughtMedias") log := svc.log.WithField("method", "GetUserBoughtMedias")
tbl := table.UserMedias tbl := table.UserMedias
@@ -229,11 +230,21 @@ func (svc *Service) Upsert(ctx context.Context, tenantId int64, item media_store
} }
tbl := table.Medias tbl := table.Medias
stmt := tbl. // if hash exists then update
INSERT(tbl.TenantID, tbl.Hash, tbl.Title, tbl.Price, tbl.Duration, tbl.Resources, tbl.Publish). stmt := tbl.SELECT(tbl.ID.AS("id")).WHERE(tbl.Hash.EQ(String(item.Hash)))
VALUES(Int(tenantId), String(item.Hash), String(item.Name), Int(item.Price()), Int(item.Duration), Json(resources.MustValue()), Bool(true)). log.Debug(stmt.DebugSql())
ON_CONFLICT(tbl.Hash).
DO_UPDATE( var m struct {
ID int64
}
if err := stmt.QueryContext(ctx, svc.db, &m); err != nil && !errors.Is(err, qrm.ErrNoRows) {
return errors.Wrapf(err, "query media by hash %s", item.Hash)
}
if m.ID > 0 {
// update media
stmt2 := tbl.
UPDATE().
SET( SET(
tbl.Title.SET(String(item.Name)), tbl.Title.SET(String(item.Name)),
tbl.Price.SET(Int(item.Price())), tbl.Price.SET(Int(item.Price())),
@@ -241,19 +252,34 @@ func (svc *Service) Upsert(ctx context.Context, tenantId int64, item media_store
tbl.Duration.SET(Int(item.Duration)), tbl.Duration.SET(Int(item.Duration)),
tbl.Publish.SET(Bool(true)), tbl.Publish.SET(Bool(true)),
tbl.UpdatedAt.SET(TimestampT(time.Now())), tbl.UpdatedAt.SET(TimestampT(time.Now())),
), ).
) WHERE(tbl.ID.EQ(Int(m.ID)))
log.Debug(stmt.DebugSql()) log.Debug(stmt2.DebugSql())
if _, err := stmt.ExecContext(ctx, svc.db); err != nil { if _, err := stmt2.ExecContext(ctx, svc.db); err != nil {
return errors.Wrapf(err, "upsert media: %s %s", item.Hash, item.Name) return errors.Wrapf(err, "update media: %s %s", item.Hash, item.Name)
}
svc.log.Infof("update media: %d %s %s", m.ID, item.Hash, item.Name)
return nil
} }
stmt3 := tbl.
INSERT(tbl.TenantID, tbl.Hash, tbl.Title, tbl.Price, tbl.Duration, tbl.Resources, tbl.Publish).
VALUES(Int(tenantId), String(item.Hash), String(item.Name), Int(item.Price()), Int(item.Duration), Json(resources.MustValue()), Bool(true)).
ON_CONFLICT(tbl.Hash).
DO_NOTHING()
log.Debug(stmt3.DebugSql())
if _, err := stmt3.ExecContext(ctx, svc.db); err != nil {
return errors.Wrapf(err, "insert into media: %s %s", item.Hash, item.Name)
}
svc.log.Infof("insert media: %s %s", item.Hash, item.Name)
return nil return nil
} }
// get video m3u8 // get video m3u8
func (svc *Service) GetM3U8(ctx context.Context, tenantId int64, types pg.MediaType, hash string, bought bool) (m3u8.Playlist, error) { func (svc *Service) GetM3U8(ctx context.Context, tenantId int64, types pg.MediaType, hash string, bought bool, token string) (m3u8.Playlist, error) {
log := svc.log.WithField("method", "GetM3U8") log := svc.log.WithField("method", "GetM3U8")
indexPath := filepath.Join(svc.storageConfig.Path, fmt.Sprintf("%d", tenantId), hash, types.String(), "index.m3u8") indexPath := filepath.Join(svc.storageConfig.Path, fmt.Sprintf("%d", tenantId), hash, types.String(), "index.m3u8")
log.Infof("m3u8 path: %s", indexPath) log.Infof("m3u8 path: %s", indexPath)
@@ -309,6 +335,9 @@ func (svc *Service) GetM3U8(ctx context.Context, tenantId int64, types pg.MediaT
return nil, errors.Wrap(err, "encode hash id") return nil, errors.Wrap(err, "encode hash id")
} }
seg.URI = fmt.Sprintf("%s/%s.%s", types, hashID, ext) seg.URI = fmt.Sprintf("%s/%s.%s", types, hashID, ext)
if token != "" {
seg.URI += fmt.Sprintf("?token=%s", token)
}
} }
return media, nil return media, nil

View File

@@ -45,15 +45,15 @@ func Test_DiscoverMedias(t *testing.T) {
func (t *ServiceTestSuite) Test_getM3U8() { func (t *ServiceTestSuite) Test_getM3U8() {
FocusConvey("Test_ffmpegVideoToM3U8", t.T(), func() { FocusConvey("Test_ffmpegVideoToM3U8", t.T(), func() {
Convey("Bought", func() { Convey("Bought", func() {
hash := "f464a6641a60e2722e4042db8fad2813" hash := "907f1c8fd92704233600ae54a1d75092"
media, err := t.Svc.GetM3U8(context.Background(), 1, pg.MediaTypeVideo, hash, true) media, err := t.Svc.GetM3U8(context.Background(), 1, pg.MediaTypeVideo, hash, true, "hello")
So(err, ShouldBeNil) So(err, ShouldBeNil)
t.T().Logf("%+v", media) t.T().Logf("%+v", media)
}) })
FocusConvey("Not Bought", func() { FocusConvey("Not Bought", func() {
hash := "f464a6641a60e2722e4042db8fad2813" hash := "907f1c8fd92704233600ae54a1d75092"
media, err := t.Svc.GetM3U8(context.Background(), 1, pg.MediaTypeVideo, hash, false) media, err := t.Svc.GetM3U8(context.Background(), 1, pg.MediaTypeVideo, hash, false, "hello")
So(err, ShouldBeNil) So(err, ShouldBeNil)
t.T().Logf("%+v", media) t.T().Logf("%+v", media)
}) })

View File

@@ -1,6 +1,8 @@
package middlewares package middlewares
import ( import (
"time"
"backend/pkg/consts" "backend/pkg/consts"
"backend/pkg/errorx" "backend/pkg/errorx"
@@ -11,13 +13,22 @@ import (
func (f *Middlewares) ParseJWT(c fiber.Ctx) error { func (f *Middlewares) ParseJWT(c fiber.Ctx) error {
tokens := c.GetReqHeaders()["Authorization"] tokens := c.GetReqHeaders()["Authorization"]
if len(tokens) == 0 { if len(tokens) == 0 {
return c.Next() queryToken := c.Query("token")
tokens = []string{queryToken}
if len(tokens) == 0 {
return c.Next()
}
} }
token := tokens[0] token := tokens[0]
claim, err := f.jwt.Parse(token) claim, err := f.jwt.Parse(token)
if err != nil { if err != nil {
c.ClearCookie("token") c.Cookie(&fiber.Cookie{
Name: "token",
Value: "",
Expires: time.Now().Add(-1 * time.Hour),
HTTPOnly: true,
})
log.Errorf("failed to parse jwt from token: %s", token) log.Errorf("failed to parse jwt from token: %s", token)
return errorx.RequestUnAuthorized return errorx.RequestUnAuthorized
} }
@@ -26,14 +37,24 @@ func (f *Middlewares) ParseJWT(c fiber.Ctx) error {
_, err = f.userSvc.GetByOpenID(c.Context(), claim.OpenID) _, err = f.userSvc.GetByOpenID(c.Context(), claim.OpenID)
if err != nil { if err != nil {
log.Errorf("failed to get user by open id(%s) from token: %s", claim.OpenID, token) log.Errorf("failed to get user by open id(%s) from token: %s", claim.OpenID, token)
c.ClearCookie("token") c.Cookie(&fiber.Cookie{
Name: "token",
Value: "",
Expires: time.Now().Add(-1 * time.Hour),
HTTPOnly: true,
})
return errorx.RequestUnAuthorized return errorx.RequestUnAuthorized
} }
_, err = f.userSvc.GetTenantBySlug(c.Context(), claim.Tenant) _, err = f.userSvc.GetTenantBySlug(c.Context(), claim.Tenant)
if err != nil { if err != nil {
log.Errorf("failed to get tenant(%s) by from token: %s", claim.Tenant, token) log.Errorf("failed to get tenant(%s) by from token: %s", claim.Tenant, token)
c.ClearCookie("token") c.Cookie(&fiber.Cookie{
Name: "token",
Value: "",
Expires: time.Now().Add(-1 * time.Hour),
HTTPOnly: true,
})
return errorx.RequestUnAuthorized return errorx.RequestUnAuthorized
} }

View File

@@ -28,7 +28,12 @@ func (f *Middlewares) WeChatAuth(c fiber.Ctx) error {
if _, err := f.jwt.Parse(jwtToken); err != nil { if _, err := f.jwt.Parse(jwtToken); err != nil {
log.WithError(err).Error("failed to parse jwt token") log.WithError(err).Error("failed to parse jwt token")
c.ClearCookie("token") c.Cookie(&fiber.Cookie{
Name: "token",
Value: "",
Expires: time.Now().Add(-1 * time.Hour),
HTTPOnly: true,
})
return c.Redirect().To(c.Path()) return c.Redirect().To(c.Path())
} }
} }

View File

@@ -33,6 +33,5 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -8,7 +8,7 @@ import (
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
hashids "github.com/speps/go-hashids/v2" "github.com/speps/go-hashids/v2"
) )
// @provider // @provider
@@ -87,7 +87,7 @@ func (c *Controller) GetChargeCodes(ctx fiber.Ctx) error {
Code string `json:"code"` Code string `json:"code"`
} }
amount := []int64{1, 5, 10, 20, 50, 100} amount := []int64{1, 2, 5, 10, 20, 50, 100, 200, 300, 500, 1000, 2000}
codes := []generateCode{} codes := []generateCode{}
for _, a := range amount { for _, a := range amount {
code, err := c.svc.GenerateChargeCode(ctx.Context(), claim.TenantID, a*100) code, err := c.svc.GenerateChargeCode(ctx.Context(), claim.TenantID, a*100)

View File

@@ -7,7 +7,7 @@ import (
"git.ipao.vip/rogeecn/atom/container" "git.ipao.vip/rogeecn/atom/container"
"git.ipao.vip/rogeecn/atom/contracts" "git.ipao.vip/rogeecn/atom/contracts"
"git.ipao.vip/rogeecn/atom/utils/opt" "git.ipao.vip/rogeecn/atom/utils/opt"
hashids "github.com/speps/go-hashids/v2" "github.com/speps/go-hashids/v2"
) )
func Provide(opts ...opt.Option) error { func Provide(opts ...opt.Option) error {
@@ -23,7 +23,6 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := container.Container.Provide(func( if err := container.Container.Provide(func(
controller *Controller, controller *Controller,
) (contracts.HttpRoute, error) { ) (contracts.HttpRoute, error) {
@@ -37,7 +36,6 @@ func Provide(opts ...opt.Option) error {
}, atom.GroupRoutes); err != nil { }, atom.GroupRoutes); err != nil {
return err return err
} }
if err := container.Container.Provide(func( if err := container.Container.Provide(func(
db *sql.DB, db *sql.DB,
hashIds *hashids.HashID, hashIds *hashids.HashID,
@@ -53,6 +51,5 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -4,6 +4,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"backend/modules/users" "backend/modules/users"
"backend/pkg/pg" "backend/pkg/pg"
@@ -78,6 +79,7 @@ func (c *Controller) Render(ctx fiber.Ctx) error {
ctx.Cookie(&fiber.Cookie{ ctx.Cookie(&fiber.Cookie{
Name: "token", Name: "token",
Value: jwtToken, Value: jwtToken,
Expires: time.Now().Add(6 * time.Hour),
HTTPOnly: true, HTTPOnly: true,
}) })

View File

@@ -27,6 +27,5 @@ func Provide(opts ...opt.Option) error {
}); err != nil { }); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -26,6 +26,5 @@ func Provide(opts ...opt.Option) error {
}, atom.GroupInitial); err != nil { }, atom.GroupInitial); err != nil {
return err return err
} }
return nil return nil
} }

View File

@@ -28,7 +28,7 @@ func (c *Controller) Prepare() error {
} }
func (c *Controller) store() { func (c *Controller) store() {
ticker := time.NewTicker(time.Minute * 5) ticker := time.NewTicker(time.Minute * 60)
for range ticker.C { for range ticker.C {
c.log.WithField("action", "store").Info("start to run store") c.log.WithField("action", "store").Info("start to run store")

View File

@@ -1,41 +0,0 @@
package uuid
import (
"git.ipao.vip/rogeecn/atom/container"
"git.ipao.vip/rogeecn/atom/utils/opt"
"github.com/gofrs/uuid"
)
func DefaultProvider() container.ProviderContainer {
return container.ProviderContainer{
Provider: Provide,
Options: []opt.Option{},
}
}
type Generator struct {
generator uuid.Generator
}
func Provide(opts ...opt.Option) error {
o := opt.New(opts...)
return container.Container.Provide(func() (*Generator, error) {
return &Generator{
generator: uuid.DefaultGenerator,
}, nil
}, o.DiOptions()...)
}
func (u *Generator) MustGenerate() string {
uuid, _ := u.Generate()
return uuid
}
func (u *Generator) Generate() (string, error) {
uuid, err := u.generator.NewV4()
if err != nil {
return "", err
}
return uuid.String(), err
}

View File

@@ -4,6 +4,7 @@
exec 1>>/var/log/cron.backupdb.log 2>&1 exec 1>>/var/log/cron.backupdb.log 2>&1
# Add timestamp to log entry # Add timestamp to log entry
start_time=$(date "+%Y-%m-%d %H:%M:%S")
echo "=== Backup started at $(date '+%Y-%m-%d %H:%M:%S') ===" echo "=== Backup started at $(date '+%Y-%m-%d %H:%M:%S') ==="
# Set variables # Set variables
@@ -54,3 +55,12 @@ rclone sync --update /opt/services/postgres/data/backups/ alioss:/rogee-backups/
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup completed: postgres_backup_${TIMESTAMP}.zip" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup completed: postgres_backup_${TIMESTAMP}.zip"
echo "=== Backup finished at $(date '+%Y-%m-%d %H:%M:%S') ===" echo "=== Backup finished at $(date '+%Y-%m-%d %H:%M:%S') ==="
end_time=$(date "+%Y-%m-%d %H:%M:%S")
/usr/bin/curl -X POST "https://gotify.jdwan.com/message?token=AknNrK_M.ZGqDz5" \
-H "Content-Type: application/json" \
-d "{
\"message\": \"Backup completed from $start_time to $end_time\",
\"title\": \"Backup Complete\",
\"priority\": 5
}"

13
cron.sh
View File

@@ -17,8 +17,17 @@ fi
start_time=$(date "+%Y-%m-%d %H:%M:%S") start_time=$(date "+%Y-%m-%d %H:%M:%S")
echo "Start sync at $start_time" >>"$LOG_FILE" echo "Start sync at $start_time" >>"$LOG_FILE"
qvyun --config /usr/local/etc/qvyun.toml tasks discover --from /mnt/ypl/publish/ --to /mnt/ypl/publish/processed/1 2>&1 >>"$LOG_FILE" /usr/local/bin/qvyun --config /usr/local/etc/qvyun.toml tasks discover --from /mnt/ypl/publish/ --to /mnt/ypl/publish/processed/1 2>&1 >>"$LOG_FILE"
rsync -avh --progress /mnt/ypl/publish/processed/ server.ali.bj.01:/data 2>&1 >>"$LOG_FILE" /usr/bin/rsync -avh --progress /mnt/ypl/publish/processed/ server.ali.bj.01:/data 2>&1 >>"$LOG_FILE"
end_time=$(date "+%Y-%m-%d %H:%M:%S") end_time=$(date "+%Y-%m-%d %H:%M:%S")
echo "End sync at $end_time" >>"$LOG_FILE" echo "End sync at $end_time" >>"$LOG_FILE"
# Send notification via Gotify
/usr/bin/curl -X POST "https://gotify.jdwan.com/message?token=AknNrK_M.ZGqDz5" \
-H "Content-Type: application/json" \
-d "{
\"message\": \"Sync completed from $start_time to $end_time\",
\"title\": \"QVYun Sync Status\",
\"priority\": 5
}"

View File

@@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"dplayer": "^1.27.1",
"hls.js": "^0.8.5", "hls.js": "^0.8.5",
"pinia": "^2.2.6", "pinia": "^2.2.6",
"vant": "^4.9.9", "vant": "^4.9.9",
@@ -871,6 +872,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/balloon-css": {
"version": "1.2.0",
"resolved": "https://npm.hub.ipao.vip/repository/npm/balloon-css/-/balloon-css-1.2.0.tgz",
"integrity": "sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==",
"license": "MIT"
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"dev": true, "dev": true,
@@ -1108,6 +1115,28 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/dplayer": {
"version": "1.27.1",
"resolved": "https://npm.hub.ipao.vip/repository/npm/dplayer/-/dplayer-1.27.1.tgz",
"integrity": "sha512-2laBMXs5V1B9zPwJ7eAIw/OBo+Xjvy03i4GHTk3Cg+IWbrq8rKMFO0fFr6ClAYotYOCcFGOvaJDkOZcgKllsCA==",
"license": "MIT",
"dependencies": {
"axios": "1.2.3",
"balloon-css": "^1.0.3",
"promise-polyfill": "8.3.0"
}
},
"node_modules/dplayer/node_modules/axios": {
"version": "1.2.3",
"resolved": "https://npm.hub.ipao.vip/repository/npm/axios/-/axios-1.2.3.tgz",
"integrity": "sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.66", "version": "1.5.66",
"dev": true, "dev": true,
@@ -1997,6 +2026,12 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/promise-polyfill": {
"version": "8.3.0",
"resolved": "https://npm.hub.ipao.vip/repository/npm/promise-polyfill/-/promise-polyfill-8.3.0.tgz",
"integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==",
"license": "MIT"
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://npm.hub.ipao.vip/repository/npm/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://npm.hub.ipao.vip/repository/npm/proxy-from-env/-/proxy-from-env-1.1.0.tgz",

View File

@@ -4,12 +4,13 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite --host 0.0.0.0",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"dplayer": "^1.27.1",
"hls.js": "^0.8.5", "hls.js": "^0.8.5",
"pinia": "^2.2.6", "pinia": "^2.2.6",
"vant": "^4.9.9", "vant": "^4.9.9",
@@ -25,4 +26,4 @@
"vite": "^5.4.10", "vite": "^5.4.10",
"vite-plugin-vue-devtools": "^7.5.4" "vite-plugin-vue-devtools": "^7.5.4"
} }
} }

View File

@@ -1,6 +1,7 @@
<template> <template>
<van-nav-bar :title="item.title" left-text="返回" left-arrow @click-left="onClickLeft" /> <van-nav-bar :title="item.title" left-text="返回" left-arrow @click-left="onClickLeft" />
<video controls id="video" :poster="item.poster" /> <!-- <video controls id="video" :poster="item.poster" /> -->
<div id="dplayer" :style="{ backgroundImage: `url(${item.poster})` }"></div>
<template v-if="false === item.bought"> <template v-if="false === item.bought">
<van-notice-bar left-icon="volume-o" text="未购买的视频、音频默认播放时长为1分钟左右。" /> <van-notice-bar left-icon="volume-o" text="未购买的视频、音频默认播放时长为1分钟左右。" />
@@ -26,32 +27,107 @@
<script setup> <script setup>
import request from "@/utils/request"; import request from "@/utils/request";
import DPlayer from "dplayer";
import Hls from "hls.js"; import Hls from "hls.js";
import { onBeforeUnmount, onMounted } from "vue"; import { onBeforeUnmount, onMounted } from "vue";
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const pageLoading = ref(true) const pageLoading = ref(true)
let player = null;
const item = ref({ const item = ref({
title: "加载中...", title: "加载中...",
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
const player = document.getElementById("video"); player?.destroy();
if (player) {
player.pause();
}
}); });
onMounted(() => { onMounted(() => {
loadMedia(route.params.hash); loadMedia(route.params.hash);
const player = document.getElementById("video");
player.addEventListener("ended", function () {
console.log("Video ended");
});
}); });
const onClickLeft = () => {
if (route.meta.from?.name) {
router.back();
return;
}
router.replace({
name: "tab.home",
params: {
tenant: route.params.tenant,
},
});
};
const loadMedia = (hash) => {
request
.get(`/medias/${hash}`)
.then((res) => {
console.log(res);
item.value = res.data;
})
.catch((err) => {
console.error("ERROR", err);
})
.finally(() => {
pageLoading.value = false
});
};
const play = (hash, type) => {
let source = `/v1/medias/${hash}/${type}`;
if (navigator.userAgent.indexOf("iPhone") > -1) {
source = `/v1/medias/${hash}/${type}?token=${__GA}`;
}
player = new DPlayer({
container: document.getElementById('dplayer'),
playbackSpeed: [0.5, 0.75, 1, 1.25, 1.5, 2],
video: {
url: source,
pic: item.value.poster,
type: 'customHls',
customType: {
customHls: function (video, player) {
const hls = new Hls({
xhrSetup: function (xhr, url) {
xhr.withCredentials = true;
xhr.setRequestHeader("Authorization", "Bearer " + __GA);
},
});
hls.loadSource(source);
hls.attachMedia(video);
},
},
}
});
player.play();
// if (Hls.isSupported()) {
// var hls = new Hls({
// xhrSetup: function (xhr, url) {
// xhr.withCredentials = true;
// },
// });
// hls.loadSource(source);
// hls.attachMedia(player);
// hls.on(Hls.Events.MANIFEST_PARSED, function () {
// player.play();
// });
// } else if (player.canPlayType("application/vnd.apple.mpegurl")) {
// player.src = source;
// player.addEventListener("loadedmetadata", function () {
// player.play();
// });
// }
};
const buy = () => { const buy = () => {
// get use balance // get use balance
request request
@@ -99,55 +175,6 @@ const buy = () => {
}; };
const onClickLeft = () => {
if (route.meta.from?.name) {
router.back();
return;
}
router.replace({
name: "tab.home",
params: {
tenant: route.params.tenant,
},
});
};
const loadMedia = (hash) => {
request
.get(`/medias/${hash}`)
.then((res) => {
console.log(res);
item.value = res.data;
})
.catch((err) => {
console.error("ERROR", err);
})
.finally(() => {
pageLoading.value = false
});
};
const play = (hash, type) => {
const player = document.getElementById("video");
const source = `/v1/medias/${hash}/${type}`;
if (Hls.isSupported()) {
var hls = new Hls({
xhrSetup: function (xhr, url) {
xhr.setRequestHeader("Authorization", "Bearer " + __GA);
},
});
hls.loadSource(source);
hls.attachMedia(player);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
player.play();
});
} else if (player.canPlayType("application/vnd.apple.mpegurl")) {
player.src = source;
player.addEventListener("loadedmetadata", function () {
player.play();
});
}
};
</script> </script>
<style scoped> <style scoped>
@@ -155,10 +182,13 @@ const play = (hash, type) => {
padding: 1em; padding: 1em;
} }
#video { #video,
#dplayer {
background-color: #cccccc; background-color: #cccccc;
aspect-ratio: 4 / 3; aspect-ratio: 16 / 9;
width: 100%; width: 100%;
object-fit: fill; object-fit: fill;
background-size: cover;
background-position: center;
} }
</style> </style>

View File

@@ -11,7 +11,7 @@ import { defineConfig } from "vite";
export default defineConfig({ export default defineConfig({
server: { server: {
proxy: { proxy: {
'^/static': { '^/poster': {
target: 'http://localhost:9600', target: 'http://localhost:9600',
changeOrigin: true, changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '') // rewrite: (path) => path.replace(/^\/api/, '')

View File

@@ -13,8 +13,9 @@
"settings": { "settings": {
"vue3snippets.enable-compile-vue-file-on-did-save-code": false, "vue3snippets.enable-compile-vue-file-on-did-save-code": false,
"cSpell.words": [ "cSpell.words": [
"dplayer",
"qvyun" "qvyun"
], ],
"git.ignoreLimitWarning": true "git.ignoreLimitWarning": true
} }
} }