chore: 发布 v1.0.1 并支持端口参数

This commit is contained in:
2026-06-23 17:01:25 +08:00
parent ae8fe7f31b
commit ebded5057f
4 changed files with 75 additions and 34 deletions

View File

@@ -1,7 +1,10 @@
package main
import (
"flag"
"fmt"
"net/http"
"os"
"time"
"hightube/internal/api"
@@ -11,9 +14,16 @@ import (
"hightube/internal/stream"
)
type serverConfig struct {
apiPort int
rtmpPort int
}
func main() {
cfg := parseFlags()
monitor.Init(2000)
monitor.Infof("Starting Hightube Server v1.0.0-Beta4.8")
monitor.Infof("Starting Hightube Server v1.0.1")
// Initialize Database and run auto-migrations
db.InitDB()
@@ -21,28 +31,52 @@ func main() {
// Initialize Chat WebSocket Hub
chat.InitChat()
srv := stream.NewRTMPServer()
srv := stream.NewRTMPServer(fmt.Sprintf("%d", cfg.rtmpPort))
// Start the API server in a goroutine so it doesn't block the RTMP server
go func() {
apiAddr := fmt.Sprintf(":%d", cfg.apiPort)
r := api.SetupRouter(srv)
httpServer := &http.Server{
Addr: ":8080",
Addr: apiAddr,
Handler: r,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20,
}
monitor.Infof("API server listening on :8080")
monitor.Infof("Web console listening on :8080/admin")
monitor.Infof("API server listening on %s", apiAddr)
monitor.Infof("Web console listening on %s/admin", apiAddr)
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
monitor.Errorf("Failed to start API server: %v", err)
}
}()
// Setup and start the RTMP server
rtmpAddr := fmt.Sprintf(":%d", cfg.rtmpPort)
monitor.Infof("Ready to receive RTMP streams from OBS")
if err := srv.Start(":1935"); err != nil {
if err := srv.Start(rtmpAddr); err != nil {
monitor.Errorf("Failed to start RTMP server: %v", err)
}
}
func parseFlags() serverConfig {
cfg := serverConfig{}
flag.IntVar(&cfg.apiPort, "api-port", 8080, "API/Web console listen port")
flag.IntVar(&cfg.rtmpPort, "rtmp-port", 1935, "RTMP listen port")
flag.Parse()
if !validPort(cfg.apiPort) {
fmt.Fprintf(os.Stderr, "invalid --api-port %d: port must be between 1 and 65535\n", cfg.apiPort)
os.Exit(2)
}
if !validPort(cfg.rtmpPort) {
fmt.Fprintf(os.Stderr, "invalid --rtmp-port %d: port must be between 1 and 65535\n", cfg.rtmpPort)
os.Exit(2)
}
return cfg
}
func validPort(port int) bool {
return port >= 1 && port <= 65535
}

View File

@@ -40,6 +40,7 @@ type RTMPServer struct {
transcoders map[string][]*variantTranscoder
thumbnailJobs map[string]context.CancelFunc
internalPublishKey string
rtmpPort string
thumbnailDir string
mutex sync.RWMutex
}
@@ -100,13 +101,14 @@ func (w *bufferedWriteFlusher) Flush() error {
return nil
}
// NewRTMPServer creates and initializes a new media server
func NewRTMPServer() *RTMPServer {
// NewRTMPServer creates and initializes a new media server.
func NewRTMPServer(rtmpPort string) *RTMPServer {
s := &RTMPServer{
channels: make(map[string]*pubsub.Queue),
transcoders: make(map[string][]*variantTranscoder),
thumbnailJobs: make(map[string]context.CancelFunc),
internalPublishKey: generateInternalPublishKey(),
rtmpPort: rtmpPort,
thumbnailDir: filepath.Join(os.TempDir(), "hightube-thumbnails"),
server: &rtmp.Server{},
}
@@ -348,8 +350,8 @@ func (s *RTMPServer) startVariantTranscoders(roomID string) {
launch := make([]*variantTranscoder, 0, len(supportedQualities))
for quality, profile := range supportedQualities {
ctx, cancel := context.WithCancel(context.Background())
inputURL := fmt.Sprintf("rtmp://127.0.0.1:1935/live/%s", roomID)
outputURL := fmt.Sprintf("rtmp://127.0.0.1:1935/variant/%s/%s/%s", roomID, quality, s.internalPublishKey)
inputURL := fmt.Sprintf("rtmp://127.0.0.1:%s/live/%s", s.rtmpPort, roomID)
outputURL := fmt.Sprintf("rtmp://127.0.0.1:%s/variant/%s/%s/%s", s.rtmpPort, roomID, quality, s.internalPublishKey)
cmd := exec.CommandContext(
ctx,
"ffmpeg",
@@ -475,7 +477,7 @@ func (s *RTMPServer) captureThumbnail(roomID string) {
"-y",
"-loglevel", "error",
"-rtmp_live", "live",
"-i", fmt.Sprintf("rtmp://127.0.0.1:1935/live/%s", roomID),
"-i", fmt.Sprintf("rtmp://127.0.0.1:%s/live/%s", s.rtmpPort, roomID),
"-frames:v", "1",
"-q:v", "4",
tempPath,

View File

@@ -124,7 +124,10 @@ class _SettingsPageState extends State<SettingsPage> {
return Scaffold(
appBar: AppBar(
title: Text(l10n.settings, style: TextStyle(fontWeight: FontWeight.bold)),
title: Text(
l10n.settings,
style: TextStyle(fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: SingleChildScrollView(
@@ -136,17 +139,21 @@ class _SettingsPageState extends State<SettingsPage> {
_buildProfileSection(auth),
const SizedBox(height: 32),
],
_buildSectionTitle(l10n.language),
const SizedBox(height: 16),
DropdownButtonFormField<Locale?>(
initialValue: settings.locale == null
? null
: AppLocalizations.supportedLocales.cast<Locale?>().firstWhere(
(l) => l?.languageCode == settings.locale?.languageCode &&
l?.scriptCode == settings.locale?.scriptCode,
orElse: () => null,
),
initialValue: settings.locale == null
? null
: AppLocalizations.supportedLocales
.cast<Locale?>()
.firstWhere(
(l) =>
l?.languageCode ==
settings.locale?.languageCode &&
l?.scriptCode == settings.locale?.scriptCode,
orElse: () => null,
),
decoration: InputDecoration(
labelText: l10n.selectLanguage,
prefixIcon: const Icon(Icons.language),
@@ -155,10 +162,7 @@ class _SettingsPageState extends State<SettingsPage> {
),
),
items: [
DropdownMenuItem(
value: null,
child: Text(l10n.system),
),
DropdownMenuItem(value: null, child: Text(l10n.system)),
DropdownMenuItem(
value: const Locale('en'),
child: Text(l10n.english),
@@ -168,7 +172,10 @@ class _SettingsPageState extends State<SettingsPage> {
child: Text(l10n.simplifiedChinese),
),
DropdownMenuItem(
value: const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
value: const Locale.fromSubtags(
languageCode: 'zh',
scriptCode: 'Hant',
),
child: Text(l10n.traditionalChinese),
),
DropdownMenuItem(
@@ -258,7 +265,10 @@ class _SettingsPageState extends State<SettingsPage> {
},
),
const SizedBox(height: 20),
Text(l10n.accentColor, style: Theme.of(context).textTheme.labelLarge),
Text(
l10n.accentColor,
style: Theme.of(context).textTheme.labelLarge,
),
const SizedBox(height: 12),
Wrap(
spacing: 12,
@@ -301,9 +311,7 @@ class _SettingsPageState extends State<SettingsPage> {
SwitchListTile.adaptive(
contentPadding: EdgeInsets.zero,
title: Text(l10n.livePreviewThumbnails),
subtitle: Text(
l10n.livePreviewThumbnailsDesc,
),
subtitle: Text(l10n.livePreviewThumbnailsDesc),
value: settings.livePreviewThumbnailsEnabled,
onChanged: settings.setLivePreviewThumbnailsEnabled,
),
@@ -392,10 +400,7 @@ class _SettingsPageState extends State<SettingsPage> {
"Hightube",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
"Version: 1.0.0-beta4.8",
style: TextStyle(color: Colors.grey),
),
Text("Version: 1.0.1", style: TextStyle(color: Colors.grey)),
Text(
"Author: Highground-Soft",
style: TextStyle(color: Colors.grey),

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0-beta4.8
version: 1.0.1
environment:
sdk: ^3.11.1