vendor/gocv
vendor/yolo
*.out
-*.mp4
\ No newline at end of file
+*.mp4
+oauth.json
\ No newline at end of file
+debug: true
externalUrl: "localhost"
serveAddr: "0.0.0.0"
-servePort: "8080"
+servePort: "8000"
maxFileSize: 838860800
stream:
turnServerAddr: "stun:stun.l.google.com:19302"
extAllowList:
- "mp4"
udpBufferByteSize: 300000
- signalPort: "8082"
- rtpReceivePort: "8084"
+ signalPort: "8002"
+ rtpReceivePort: "8004"
utils:
useCompress: false
--- /dev/null
+package controller
+
+import "github.com/gin-gonic/gin"
+
+func GetIndex(c *gin.Context) {
+
+ c.HTML(200, "index.html", gin.H{
+ "title": "Index",
+ })
+}
+
+func GetSigninIndex(c *gin.Context) {
+
+ c.HTML(200, "signin.html", gin.H{
+ "title": "Signin",
+ })
+
+}
)
type SOLIAGAIN_CONFIG struct {
+ Debug bool `yaml:"debug"`
ExternalUrl string `yaml:"externalUrl"`
ServeAddr string `yaml:"serveAddr"`
ServePort string `yaml:"servePort"`
import (
"time"
+ "github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
+ pkgauth "github.com/seantywork/sorrylinus-again/pkg/auth"
pkgstream "github.com/seantywork/sorrylinus-again/pkg/stream"
pkgutils "github.com/seantywork/sorrylinus-again/pkg/utils"
)
genserver := gin.Default()
- genserver.MaxMultipartMemory = CONF.MaxFileSize
+ store := sessions.NewCookieStore([]byte("SOLIAGAIN"))
+
+ genserver.Use(sessions.Sessions("SOLIAGAIN", store))
+
+ ConfigureRuntime(genserver)
+
+ RegisterRoutes(genserver)
+
+ return genserver
+
+}
+
+func ConfigureRuntime(e *gin.Engine) {
+
+ e.MaxMultipartMemory = CONF.MaxFileSize
+
+ pkgauth.DEBUG = CONF.Debug
pkgstream.EXTERNAL_URL = CONF.ExternalUrl
pkgutils.USE_COMPRESS = CONF.Utils.UseCompress
+}
+
+func RegisterRoutes(e *gin.Engine) {
+
// base
- genserver.LoadHTMLGlob("view/*")
+ e.LoadHTMLGlob("view/*")
- genserver.Static("/public", "./public")
+ e.Static("/public", "./public")
- // stream
+ e.GET("/", GetIndex)
- // cctv
+ e.GET("/signin", GetSigninIndex)
- genserver.GET("/cctv", pkgstream.GetCCTVIndex)
+ e.GET("/api/oauth2/google/signin", pkgauth.OauthGoogleLogin)
- genserver.POST("/cctv/create", pkgstream.PostCCTVCreate)
+ e.GET("/oauth2/google/callback", pkgauth.OauthGoogleCallback)
- // cctv local
+ pkgauth.InitAuth()
- genserver.GET("/cctv/local", pkgstream.GetCCTVLocalIndex)
+ // stream
- genserver.GET("/cctv/local/turn/address", pkgstream.GetCCTVLocalTurnServeAddr)
+ // cctv
- genserver.POST("/cctv/local/offer", pkgstream.PostCCTVLocalOffer)
+ e.GET("/cctv", pkgstream.GetCCTVIndex)
- // video
+ e.POST("/api/cctv/create", pkgstream.PostCCTVCreate)
- genserver.GET("/video", pkgstream.GetVideoIndex)
+ e.POST("/api/cctv/delete", pkgstream.PostCCTVDelete)
- genserver.GET("/video/watch", pkgstream.GetVideoWatchPage)
+ go pkgstream.InitRTMPServer()
- genserver.POST("/video/upload", pkgstream.PostVideoUpload)
+ // video
- genserver.GET("/video/watch/c/:contentId", pkgstream.GetVideoWatchContentByID)
+ e.GET("/video", pkgstream.GetVideoIndex)
- // peers
+ e.GET("/api/video/watch", pkgstream.GetVideoWatchPage)
- genserver.GET("/peers", pkgstream.GetPeersIndex)
+ e.POST("/api/video/upload", pkgstream.PostVideoUpload)
- genserver.GET("/peers/signal/address", pkgstream.GetPeersSignalAddress)
+ e.GET("/api/video/watch/c/:contentId", pkgstream.GetVideoWatchContentByID)
- go pkgstream.InitPeersSignalOn("/peers/signal")
+ // peers
- // utils
+ e.GET("/peers", pkgstream.GetPeersIndex)
- return genserver
+ e.GET("/api/peers/signal/address", pkgstream.GetPeersSignalAddress)
+
+ go pkgstream.InitPeersSignalOn("/ch/peers/signal")
}
--- /dev/null
+FROM mysql:8
+
+ENV MYSQL_ROOT_PASSWORD letsshareitwiththewholeuniverse
+ENV MYSQL_HOST '%'
+
+EXPOSE 3306
+
+ADD ./init.sql /docker-entrypoint-initdb.d
\ No newline at end of file
--- /dev/null
+CREATE USER 'seantywork'@'%' IDENTIFIED BY 'letsshareitwiththewholeuniverse';
+
+GRANT ALL PRIVILEGES ON *.* TO 'seantywork'@'%';
+
+CREATE DATABASE sorrylinusdb;
+
+USE sorrylinusdb;
+
+CREATE TABLE users (
+ id VARCHAR(128),
+ pw VARCHAR(256),
+ sess VARCHAR(128),
+ PRIMARY KEY(id)
+ );
+
+CREATE TABLE admin (
+ id VARCHAR(128),
+ PRIMARY KEY(id)
+ );
+
+
+COMMIT;
\ No newline at end of file
)
require (
+ github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/bytedance/sonic v1.11.8 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
+ github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
+ github.com/gorilla/context v1.1.2 // indirect
+ github.com/gorilla/securecookie v1.1.2 // indirect
+ github.com/gorilla/sessions v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
+github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
+github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LRA=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 h1:EUFmvQ8ffefnSAmaUZd9HZYZSw9w/bFjp3FiNaJ5WmE=
+github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
+github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
+github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
+github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
+github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
+github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
+github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
server := solictl.CreateServer()
- server.Run()
+ server.Run(solictl.CONF.ServeAddr + ":" + solictl.CONF.ServePort)
}
--- /dev/null
+package auth
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/gin-gonic/contrib/sessions"
+ "github.com/gin-gonic/gin"
+)
+
+var DEBUG bool = false
+
+func OauthGoogleLogin(c *gin.Context) {
+
+ oauth_state := GenerateStateAuthCookie(c)
+
+ u := GoogleOauthConfig.AuthCodeURL(oauth_state)
+
+ c.Redirect(302, u)
+
+}
+
+func OauthGoogleCallback(c *gin.Context) {
+
+ session := sessions.Default(c)
+
+ var session_id string
+
+ v := session.Get("SOLIAGAIN")
+
+ if v == nil {
+ fmt.Printf("access auth failed: %s\n", "session id not found")
+ return
+ } else {
+ session_id = v.(string)
+ }
+
+ state := c.Request.FormValue("state")
+
+ if state == "" {
+ fmt.Printf("access auth failed: %s\n", "form state not found")
+ return
+ }
+
+ if state != session_id {
+ fmt.Printf("access auth failed: %s\n", "value not matching")
+ c.Redirect(302, "/signin")
+ return
+ }
+
+ data, err := GetUserDataFromGoogle(c.Request.FormValue("code"))
+ if err != nil {
+ fmt.Printf("access auth failed: %s\n", err.Error())
+ c.Redirect(302, "/signin")
+ return
+ }
+
+ var oauth_struct OAuthStruct
+
+ err = json.Unmarshal(data, &oauth_struct)
+
+ if err != nil {
+ fmt.Printf("access auth failed: %s\n", err.Error())
+ c.Redirect(302, "/signin")
+ return
+ }
+
+ if !oauth_struct.VERIFIED_EMAIL {
+ fmt.Printf("access auth failed: %s\n", err.Error())
+ c.Redirect(302, "/signin")
+ return
+ }
+
+ if err != nil {
+ fmt.Printf("access auth failed: %s\n", err.Error())
+ c.Redirect(302, "/signin")
+ return
+ }
+
+ fmt.Println("oauth sign in success")
+
+ c.Redirect(302, "/")
+
+}
--- /dev/null
+package auth
+
+import (
+ "context"
+ "crypto/rand"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+
+ "github.com/gin-gonic/contrib/sessions"
+ "github.com/gin-gonic/gin"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+)
+
+type OAuthJSON struct {
+ Web struct {
+ ClientID string `json:"client_id"`
+ ProjectID string `json:"project_id"`
+ AuthURI string `json:"auth_uri"`
+ TokenURI string `json:"token_uri"`
+ AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"`
+ ClientSecret string `json:"client_secret"`
+ RedirectUris []string `json:"redirect_uris"`
+ } `json:"web"`
+}
+
+const OauthGoogleUrlAPI = "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
+
+type OAuthStruct struct {
+ ID string `json:"id"`
+ EMAIL string `json:"email"`
+ VERIFIED_EMAIL bool `json:"verified_email"`
+ PICTURE string `json:"picture"`
+}
+
+var OAUTH_JSON OAuthJSON
+
+var GoogleOauthConfig *oauth2.Config
+
+func InitAuth() {
+
+ OAUTH_JSON = GetOAuthJSON()
+
+ GoogleOauthConfig = GenerateGoogleOauthConfig()
+
+}
+
+func GetOAuthJSON() OAuthJSON {
+
+ var cj OAuthJSON
+
+ file_byte, err := os.ReadFile("oauth.json")
+
+ if err != nil {
+ panic(err)
+ }
+
+ err = json.Unmarshal(file_byte, &cj)
+
+ if err != nil {
+ panic(err)
+ }
+
+ return cj
+
+}
+
+func GenerateGoogleOauthConfig() *oauth2.Config {
+
+ google_oauth_config := &oauth2.Config{
+ ClientID: OAUTH_JSON.Web.ClientID,
+ ClientSecret: OAUTH_JSON.Web.ClientSecret,
+ Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
+ Endpoint: google.Endpoint,
+ }
+
+ if DEBUG {
+
+ google_oauth_config.RedirectURL = OAUTH_JSON.Web.RedirectUris[0]
+
+ } else {
+
+ google_oauth_config.RedirectURL = OAUTH_JSON.Web.RedirectUris[1]
+ }
+
+ fmt.Println(google_oauth_config.RedirectURL)
+
+ return google_oauth_config
+
+}
+
+func GenerateStateAuthCookie(c *gin.Context) string {
+
+ b := make([]byte, 64)
+ rand.Read(b)
+
+ state := base64.URLEncoding.EncodeToString(b)
+
+ session := sessions.Default(c)
+
+ session.Set("SOLIAGAIN", state)
+ session.Save()
+
+ return state
+}
+
+func GetUserDataFromGoogle(code string) ([]byte, error) {
+
+ token, err := GoogleOauthConfig.Exchange(context.Background(), code)
+ if err != nil {
+ return nil, fmt.Errorf("code exchange wrong: %s", err.Error())
+ }
+ response, err := http.Get(OauthGoogleUrlAPI + token.AccessToken)
+ if err != nil {
+ return nil, fmt.Errorf("failed getting user info: %s", err.Error())
+ }
+ defer response.Body.Close()
+ contents, err := io.ReadAll(response.Body)
+ if err != nil {
+ return nil, fmt.Errorf("failed read response: %s", err.Error())
+ }
+ return contents, nil
+}
--- /dev/null
+package dbquery
--- /dev/null
+package sorrylinushub
"bytes"
"encoding/binary"
"encoding/json"
+ "fmt"
"io"
"log"
"net"
rtmpmsg "github.com/yutopp/go-rtmp/message"
)
+var RTP_RECEIVE_ADDR string
+
+var RTP_RECEIVE_PORT string
+
+var RTP_CONSUMERS = make(map[string]RTMPWebRTCPeer)
+
+const RTP_HEADER_LENGTH_FIELD = 4
+
+var TEST_KEY string = "foobar"
+
+type RTMPHandler struct {
+ rtmp.DefaultHandler
+ PublisherKey string
+}
+
+type RTMPWebRTCPeer struct {
+ peerConnection *webrtc.PeerConnection
+ videoTrack *webrtc.TrackLocalStaticSample
+ audioTrack *webrtc.TrackLocalStaticSample
+}
+
func GetCCTVIndex(c *gin.Context) {
c.HTML(200, "cctv.html", gin.H{
}
func PostCCTVCreate(c *gin.Context) {
+
log.Println("Incoming HTTP Request")
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{})
panic(err)
}
+ var req CLIENT_REQ
+
var offer webrtc.SessionDescription
- if err := json.NewDecoder(c.Request.Body).Decode(&offer); err != nil {
+
+ if err := c.BindJSON(&req); err != nil {
+
+ panic(err)
+
+ }
+
+ err = json.Unmarshal([]byte(req.Data), &offer)
+
+ if err != nil {
+
panic(err)
}
}
<-gatherComplete
+ /*
+
+ TODO:
+ remove test key
+
+ */
+ RTP_CONSUMERS[TEST_KEY] = RTMPWebRTCPeer{
+ peerConnection: peerConnection,
+ videoTrack: videoTrack,
+ audioTrack: audioTrack,
+ }
+
c.JSON(200, peerConnection.LocalDescription())
- go startRTMPServer(peerConnection, videoTrack, audioTrack)
}
-func startRTMPServer(peerConnection *webrtc.PeerConnection, videoTrack, audioTrack *webrtc.TrackLocalStaticSample) {
+func PostCCTVDelete(c *gin.Context) {
+
+ /*
+
+ TODO:
+ sorrylinus exchange
+
+ */
+
+ var resp SERVER_RE
+
+ resp.Status = "success"
+ resp.Reply = ""
+
+ c.JSON(200, resp)
+}
+
+func InitRTMPServer() {
log.Println("Starting RTMP Server")
- tcpAddr, err := net.ResolveTCPAddr("tcp", ":8084")
+ tcpAddr, err := net.ResolveTCPAddr("tcp", RTP_RECEIVE_ADDR+":"+RTP_RECEIVE_PORT)
if err != nil {
log.Panicf("Failed: %+v", err)
}
srv := rtmp.NewServer(&rtmp.ServerConfig{
OnConnect: func(conn net.Conn) (io.ReadWriteCloser, *rtmp.ConnConfig) {
return conn, &rtmp.ConnConfig{
- Handler: &Handler{
- peerConnection: peerConnection,
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- },
+ Handler: &RTMPHandler{},
ControlState: rtmp.StreamControlStateConfig{
DefaultBandwidthWindowSize: 6 * 1024 * 1024 / 8,
}
}
-type Handler struct {
- rtmp.DefaultHandler
- peerConnection *webrtc.PeerConnection
- videoTrack, audioTrack *webrtc.TrackLocalStaticSample
-}
-
-func (h *Handler) OnServe(conn *rtmp.Conn) {
+func (h *RTMPHandler) OnServe(conn *rtmp.Conn) {
}
-func (h *Handler) OnConnect(timestamp uint32, cmd *rtmpmsg.NetConnectionConnect) error {
+func (h *RTMPHandler) OnConnect(timestamp uint32, cmd *rtmpmsg.NetConnectionConnect) error {
log.Printf("OnConnect: %#v", cmd)
return nil
}
-func (h *Handler) OnCreateStream(timestamp uint32, cmd *rtmpmsg.NetConnectionCreateStream) error {
+func (h *RTMPHandler) OnCreateStream(timestamp uint32, cmd *rtmpmsg.NetConnectionCreateStream) error {
log.Printf("OnCreateStream: %#v", cmd)
return nil
}
-func (h *Handler) OnPublish(ctx *rtmp.StreamContext, timestamp uint32, cmd *rtmpmsg.NetStreamPublish) error {
+func (h *RTMPHandler) OnPublish(ctx *rtmp.StreamContext, timestamp uint32, cmd *rtmpmsg.NetStreamPublish) error {
log.Printf("OnPublish: %#v", cmd)
if cmd.PublishingName == "" {
- return errors.New("PublishingName is empty")
+
+ log.Printf("publishing name is empty")
+
+ return errors.New("publishing name is empty")
}
+
+ /*
+
+ TODO:
+ key validation
+
+ */
+
+ h.PublisherKey = cmd.PublishingName
+
return nil
}
-func (h *Handler) OnAudio(timestamp uint32, payload io.Reader) error {
+func (h *RTMPHandler) OnAudio(timestamp uint32, payload io.Reader) error {
var audio flvtag.AudioData
+
+ consumer, okay := RTP_CONSUMERS[h.PublisherKey]
+
+ if !okay {
+
+ return fmt.Errorf("invalid publisher")
+
+ }
+
+ consumerAudioTrack := consumer.audioTrack
+
if err := flvtag.DecodeAudioData(payload, &audio); err != nil {
return err
}
return err
}
- return h.audioTrack.WriteSample(media.Sample{
+ return consumerAudioTrack.WriteSample(media.Sample{
Data: data.Bytes(),
Duration: 128 * time.Millisecond,
})
}
-const headerLengthField = 4
-
-func (h *Handler) OnVideo(timestamp uint32, payload io.Reader) error {
+func (h *RTMPHandler) OnVideo(timestamp uint32, payload io.Reader) error {
var video flvtag.VideoData
+
+ consumer, okay := RTP_CONSUMERS[h.PublisherKey]
+
+ if !okay {
+
+ return fmt.Errorf("invalid publisher")
+
+ }
+
+ consumerVideoTrack := consumer.videoTrack
+
if err := flvtag.DecodeVideoData(payload, &video); err != nil {
return err
}
outBuf := []byte{}
videoBuffer := data.Bytes()
for offset := 0; offset < len(videoBuffer); {
- bufferLength := int(binary.BigEndian.Uint32(videoBuffer[offset : offset+headerLengthField]))
+ bufferLength := int(binary.BigEndian.Uint32(videoBuffer[offset : offset+RTP_HEADER_LENGTH_FIELD]))
if offset+bufferLength >= len(videoBuffer) {
break
}
- offset += headerLengthField
+ offset += RTP_HEADER_LENGTH_FIELD
outBuf = append(outBuf, []byte{0x00, 0x00, 0x00, 0x01}...)
outBuf = append(outBuf, videoBuffer[offset:offset+bufferLength]...)
offset += int(bufferLength)
}
- return h.videoTrack.WriteSample(media.Sample{
+ return consumerVideoTrack.WriteSample(media.Sample{
Data: outBuf,
Duration: time.Second / 30,
})
}
-func (h *Handler) OnClose() {
+func (h *RTMPHandler) OnClose() {
log.Printf("OnClose")
}
var UDP_BUFFER_BYTE_SIZE int
-var RTP_RECEIVE_ADDR string
-
-var RTP_RECEIVE_PORT string
-
var RECV_STARTED int = 0
func GetCCTVLocalIndex(c *gin.Context) {
"github.com/gin-gonic/gin"
)
+var PEERS_SIGNAL_PATH string
+
func GetPeersIndex(c *gin.Context) {
c.HTML(200, "peers.html", gin.H{
func GetPeersSignalAddress(c *gin.Context) {
- s_addr := EXTERNAL_URL + ":" + SIGNAL_PORT + "/peers/signal"
+ s_addr := EXTERNAL_URL + ":" + SIGNAL_PORT + PEERS_SIGNAL_PATH
c.JSON(http.StatusOK, SERVER_RE{Status: "success", Reply: s_addr})
func InitPeersSignalOn(peerSignalPath string) {
+ PEERS_SIGNAL_PATH = peerSignalPath
+
runPeersSignalHandlerForWS(peerSignalPath)
}
type SIGNAL_INFO struct {
Command string `json:"command"`
- UserID string `json:"user_id"`
+ Status string `json:"status"`
Data string `json:"data"`
}
var listLock sync.RWMutex
-var peerConnections []peerConnectionState
-var trackLocals map[string]*webrtc.TrackLocalStaticRTP
+var peerConnections = make([]peerConnectionState, 0)
+var trackLocals = make(map[string]*webrtc.TrackLocalStaticRTP)
type peerConnectionState struct {
peerConnection *webrtc.PeerConnection
}
- uid := sinfo.UserID
+ uid := sinfo.Data
old_c, okay := USER_SIGNAL[uid]
new_uinfo := SIGNAL_INFO{
Command: "ADDUSER",
- UserID: uid,
+ Data: uid,
}
v.WriteJSON(&new_uinfo)
old_uinfo := SIGNAL_INFO{
Command: "ADDUSER",
- UserID: k,
+ Data: k,
}
c.WriteJSON(&old_uinfo)
--- /dev/null
+pc = {}
+
+
+
+async function initCCTV(){
+
+ pc = new RTCPeerConnection()
+ pc.ontrack = function (event) {
+ var el = document.createElement(event.track.kind)
+ el.srcObject = event.streams[0]
+ el.autoplay = true
+ el.controls = true
+
+ document.getElementById('rtmpFeed').appendChild(el)
+ }
+
+ pc.addTransceiver('video')
+ pc.addTransceiver('audio')
+
+ let offer = await pc.createOffer()
+
+ pc.setLocalDescription(offer)
+
+ console.log(offer)
+
+ let req = {
+ data: JSON.stringify(offer)
+ }
+
+ let resp = await axios.post("/api/cctv/create", req)
+
+ pc.setRemoteDescription(resp.data)
+
+ console.log("init success")
+
+}
+
+
+function closeCCTV(){
+
+
+
+}
offerjson.data = offer_val
- let result = await axios.post("/cctv/offer", offerjson)
+ let result = await axios.post("/api/cctv/offer", offerjson)
if (result.data.status != "success") {
async function getTurnServerAddressAndInit(){
- let result = await axios.get("/cctv/turn/address")
+ let result = await axios.get("/api/cctv/turn/address")
if(result.data.status != "success"){
if (!offer) {
return console.log('failed to parse answer')
}
+
pc.setRemoteDescription(offer)
pc.createAnswer().then(function(answer) {
pc.setLocalDescription(answer)
sdpjson.data = btoa(JSON.stringify(pcSender.localDescription))
- let resp = await axios.post("/peers/room/sdp/m/" + meetingId + "/c/"+ userId + "/s/" + true, sdpjson)
+ let resp = await axios.post("/api/peers/room/sdp/m/" + meetingId + "/c/"+ userId + "/s/" + true, sdpjson)
pcSender.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(resp.data.reply))))
<html>
<head>
<title> RTMP to WebRTC </title>
+ <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<h1> RTMP to WebRTC </h1>
<div id="rtmpFeed"></div>
- </body>
- <script>
- let pc = new RTCPeerConnection()
- pc.ontrack = function (event) {
- var el = document.createElement(event.track.kind)
- el.srcObject = event.streams[0]
- el.autoplay = true
- el.controls = true
+ <button onclick="initCCTV()"> start cctv </button>
+
+
+ </body>
- document.getElementById('rtmpFeed').appendChild(el)
- }
+ <script src="/public/js/cctv.js"></script>
- pc.addTransceiver('video')
- pc.addTransceiver('audio')
- pc.createOffer()
- .then(offer => {
- pc.setLocalDescription(offer)
- console.log(offer)
- return fetch(`/cctv/create`, {
- method: 'post',
- headers: {
- 'Accept': 'application/json, text/plain, */*',
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(offer)
- })
- })
- .then(res => res.json())
- .then(res => pc.setRemoteDescription(res))
- .catch(alert)
- </script>
</html>
\ No newline at end of file
--- /dev/null
+<!doctype html>
+<html>
+ <head>
+ <title>sorrylinus again</title>
+ </head>
+ <body>
+ <p> sorrylinus again </p>
+ </body>
+</html>
\ No newline at end of file
<html>
<head>
<meta charset="utf-8">
+ <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
+ <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
+
</head>
<body>
<h3> Local Video </h3>
--- /dev/null
+
+<html>
+
+<head>
+
+
+</head>
+
+<body>
+<div></div>
+<main>
+ <center>
+ <div></div>
+ <div>
+ <div>
+ <form method="post" action="#">
+ <div>
+ <div>
+ </div>
+ </div>
+
+ <div>
+ <div>
+ <input type='email' name='email' id='email'/>
+ <label for='email'>Enter your email</label>
+ </div>
+ </div>
+
+ <div>
+ <div>
+ <input type='password' name='password' id='password'/>
+ <label for='password'>Enter your password</label>
+ </div>
+ <label>
+ <a href='#!'><b>Forgot Password?</b></a>
+ </label>
+ </div>
+
+ <br/>
+ <div>
+ <button type='submit' name='btn_login'>
+ Login
+ </button>
+ </div>
+ </form>
+
+ <div>
+ <div>
+ <a href="/api/oauth2/google/signin">
+ <div class="left">
+ <img width="30px" alt="Google "G" Logo"
+ src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/512px-Google_%22G%22_Logo.svg.png"/>
+ </div>
+ Login with Google
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <a href="#!">Create account</a>
+ </center>
+
+ <div class="section"></div>
+ <div class="section"></div>
+</main>
+
+</body>
+
+</html>
\ No newline at end of file