├── AlphaBlending.go ├── Composite.go ├── Dispatcher.go ├── DispatcherOneToMany.go ├── EndOfStream.go ├── HttpEndpoint.go ├── MediaProfileSpecType.go ├── Mixer.go ├── PlayerEndpoint.go ├── README.md ├── RecorderEndpoint.go ├── RtpEndpoint.go ├── WerbrtcEndpoint.go ├── base.go ├── complexTypes.go ├── core.go └── websocket.go /AlphaBlending.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IAlphaBlending interface { 6 | SetMaster(source HubPort, zOrder int) error 7 | SetPortProperties(relativeX float64, relativeY float64, zOrder int, relativeWidth float64, relativeHeight float64, port HubPort) error 8 | } 9 | 10 | // A `Hub` that mixes the :rom:attr:`MediaType.AUDIO` stream of its connected 11 | // sources and constructs one output with :rom:attr:`MediaType.VIDEO` 12 | // streams of its connected sources into its sink 13 | type AlphaBlending struct { 14 | Hub 15 | } 16 | 17 | // Return contructor params to be called by "Create". 18 | func (elem *AlphaBlending) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 19 | 20 | // Create basic constructor params 21 | ret := map[string]interface{}{ 22 | "mediaPipeline": fmt.Sprintf("%s", from), 23 | } 24 | 25 | // then merge options 26 | mergeOptions(ret, options) 27 | 28 | return ret 29 | 30 | } 31 | 32 | // Sets the source port that will be the master entry to the mixer 33 | func (elem *AlphaBlending) SetMaster(source HubPort, zOrder int) error { 34 | req := elem.getInvokeRequest() 35 | 36 | params := make(map[string]interface{}) 37 | 38 | setIfNotEmpty(params, "source", source) 39 | setIfNotEmpty(params, "zOrder", zOrder) 40 | 41 | req["params"] = map[string]interface{}{ 42 | "operation": "setMaster", 43 | "object": elem.Id, 44 | "operationParams": params, 45 | } 46 | 47 | // Call server and wait response 48 | response := <-elem.connection.Request(req) 49 | 50 | // Returns error or nil 51 | return response.Error 52 | 53 | } 54 | 55 | // Configure the blending mode of one port. 56 | func (elem *AlphaBlending) SetPortProperties(relativeX float64, relativeY float64, zOrder int, relativeWidth float64, relativeHeight float64, port HubPort) error { 57 | req := elem.getInvokeRequest() 58 | 59 | params := make(map[string]interface{}) 60 | 61 | setIfNotEmpty(params, "relativeX", relativeX) 62 | setIfNotEmpty(params, "relativeY", relativeY) 63 | setIfNotEmpty(params, "zOrder", zOrder) 64 | setIfNotEmpty(params, "relativeWidth", relativeWidth) 65 | setIfNotEmpty(params, "relativeHeight", relativeHeight) 66 | setIfNotEmpty(params, "port", port) 67 | 68 | req["params"] = map[string]interface{}{ 69 | "operation": "setPortProperties", 70 | "object": elem.Id, 71 | "operationParams": params, 72 | } 73 | 74 | // Call server and wait response 75 | response := <-elem.connection.Request(req) 76 | 77 | // Returns error or nil 78 | return response.Error 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Composite.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IComposite interface { 6 | } 7 | 8 | // A `Hub` that mixes the :rom:attr:`MediaType.AUDIO` stream of its connected 9 | // sources and constructs a grid with the :rom:attr:`MediaType.VIDEO` 10 | // streams of its connected sources into its sink 11 | type Composite struct { 12 | Hub 13 | } 14 | 15 | // Return contructor params to be called by "Create". 16 | func (elem *Composite) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 17 | 18 | // Create basic constructor params 19 | ret := map[string]interface{}{ 20 | "mediaPipeline": fmt.Sprintf("%s", from), 21 | } 22 | 23 | // then merge options 24 | mergeOptions(ret, options) 25 | 26 | return ret 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Dispatcher.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IDispatcher interface { 6 | Connect(source HubPort, sink HubPort) error 7 | } 8 | 9 | // A `Hub` that allows routing between arbitrary port pairs 10 | type Dispatcher struct { 11 | Hub 12 | } 13 | 14 | // Return contructor params to be called by "Create". 15 | func (elem *Dispatcher) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 16 | 17 | // Create basic constructor params 18 | ret := map[string]interface{}{ 19 | "mediaPipeline": fmt.Sprintf("%s", from), 20 | } 21 | 22 | // then merge options 23 | mergeOptions(ret, options) 24 | 25 | return ret 26 | 27 | } 28 | 29 | // Connects each corresponding :rom:enum:`MediaType` of the given source port with 30 | // the sink port. 31 | func (elem *Dispatcher) Connect(source HubPort, sink HubPort) error { 32 | req := elem.getInvokeRequest() 33 | 34 | params := make(map[string]interface{}) 35 | 36 | setIfNotEmpty(params, "source", source) 37 | setIfNotEmpty(params, "sink", sink) 38 | 39 | req["params"] = map[string]interface{}{ 40 | "operation": "connect", 41 | "object": elem.Id, 42 | "operationParams": params, 43 | } 44 | 45 | // Call server and wait response 46 | response := <-elem.connection.Request(req) 47 | 48 | // Returns error or nil 49 | return response.Error 50 | 51 | } 52 | -------------------------------------------------------------------------------- /DispatcherOneToMany.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IDispatcherOneToMany interface { 6 | SetSource(source HubPort) error 7 | RemoveSource() error 8 | } 9 | 10 | // A `Hub` that sends a given source to all the connected sinks 11 | type DispatcherOneToMany struct { 12 | Hub 13 | } 14 | 15 | // Return contructor params to be called by "Create". 16 | func (elem *DispatcherOneToMany) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 17 | 18 | // Create basic constructor params 19 | ret := map[string]interface{}{ 20 | "mediaPipeline": fmt.Sprintf("%s", from), 21 | } 22 | 23 | // then merge options 24 | mergeOptions(ret, options) 25 | 26 | return ret 27 | 28 | } 29 | 30 | // Sets the source port that will be connected to the sinks of every `HubPort` of 31 | // the dispatcher 32 | func (elem *DispatcherOneToMany) SetSource(source HubPort) error { 33 | req := elem.getInvokeRequest() 34 | 35 | params := make(map[string]interface{}) 36 | 37 | setIfNotEmpty(params, "source", source) 38 | 39 | req["params"] = map[string]interface{}{ 40 | "operation": "setSource", 41 | "object": elem.Id, 42 | "operationParams": params, 43 | } 44 | 45 | // Call server and wait response 46 | response := <-elem.connection.Request(req) 47 | 48 | // Returns error or nil 49 | return response.Error 50 | 51 | } 52 | 53 | // Remove the source port and stop the media pipeline. 54 | func (elem *DispatcherOneToMany) RemoveSource() error { 55 | req := elem.getInvokeRequest() 56 | 57 | req["params"] = map[string]interface{}{ 58 | "operation": "removeSource", 59 | "object": elem.Id, 60 | } 61 | 62 | // Call server and wait response 63 | response := <-elem.connection.Request(req) 64 | 65 | // Returns error or nil 66 | return response.Error 67 | 68 | } 69 | -------------------------------------------------------------------------------- /EndOfStream.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | -------------------------------------------------------------------------------- /HttpEndpoint.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IHttpGetEndpoint interface { 6 | } 7 | 8 | // An "HttpGetEndpoint" contains SOURCE pads for AUDIO and VIDEO, delivering media 9 | // using HTML5 pseudo-streaming mechanism. 10 | // This type of endpoint provide unidirectional communications. Its `MediaSink` 11 | // is associated with the HTTP GET method 12 | type HttpGetEndpoint struct { 13 | HttpEndpoint 14 | } 15 | 16 | // Return contructor params to be called by "Create". 17 | func (elem *HttpGetEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 18 | 19 | // Create basic constructor params 20 | ret := map[string]interface{}{ 21 | "mediaPipeline": fmt.Sprintf("%s", from), 22 | "terminateOnEOS": fmt.Sprintf("%s", from), 23 | "mediaProfile": fmt.Sprintf("%s", from), 24 | "disconnectionTimeout": 2, 25 | } 26 | 27 | // then merge options 28 | mergeOptions(ret, options) 29 | 30 | return ret 31 | 32 | } 33 | 34 | type IHttpPostEndpoint interface { 35 | } 36 | 37 | // An `HttpPostEndpoint` contains SINK pads for AUDIO and VIDEO, which provide 38 | // access to an HTTP file upload function 39 | // This type of endpoint provide unidirectional communications. Its 40 | // `MediaSources ` are accessed through the `HTTP` POST method. 41 | type HttpPostEndpoint struct { 42 | HttpEndpoint 43 | } 44 | 45 | // Return contructor params to be called by "Create". 46 | func (elem *HttpPostEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 47 | 48 | // Create basic constructor params 49 | ret := map[string]interface{}{ 50 | "mediaPipeline": fmt.Sprintf("%s", from), 51 | "disconnectionTimeout": 2, 52 | "useEncodedMedia": fmt.Sprintf("%s", from), 53 | } 54 | 55 | // then merge options 56 | mergeOptions(ret, options) 57 | 58 | return ret 59 | 60 | } 61 | 62 | type IHttpEndpoint interface { 63 | GetUrl() (string, error) 64 | } 65 | 66 | // Endpoint that enables Kurento to work as an HTTP server, allowing peer HTTP 67 | // clients to access media. 68 | type HttpEndpoint struct { 69 | SessionEndpoint 70 | } 71 | 72 | // Return contructor params to be called by "Create". 73 | func (elem *HttpEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 74 | return options 75 | 76 | } 77 | 78 | // Obtains the URL associated to this endpoint 79 | // Returns: 80 | // // The url as a String 81 | func (elem *HttpEndpoint) GetUrl() (string, error) { 82 | req := elem.getInvokeRequest() 83 | 84 | req["params"] = map[string]interface{}{ 85 | "operation": "getUrl", 86 | "object": elem.Id, 87 | } 88 | 89 | // Call server and wait response 90 | response := <-elem.connection.Request(req) 91 | 92 | // // The url as a String 93 | 94 | return response.Result["value"], response.Error 95 | 96 | } 97 | -------------------------------------------------------------------------------- /MediaProfileSpecType.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | -------------------------------------------------------------------------------- /Mixer.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IMixer interface { 6 | Connect(media MediaType, source HubPort, sink HubPort) error 7 | Disconnect(media MediaType, source HubPort, sink HubPort) error 8 | } 9 | 10 | // A `Hub` that allows routing of video between arbitrary port pairs and mixing of 11 | // audio among several ports 12 | type Mixer struct { 13 | Hub 14 | } 15 | 16 | // Return contructor params to be called by "Create". 17 | func (elem *Mixer) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 18 | 19 | // Create basic constructor params 20 | ret := map[string]interface{}{ 21 | "mediaPipeline": fmt.Sprintf("%s", from), 22 | } 23 | 24 | // then merge options 25 | mergeOptions(ret, options) 26 | 27 | return ret 28 | 29 | } 30 | 31 | // Connects each corresponding :rom:enum:`MediaType` of the given source port with 32 | // the sink port. 33 | func (elem *Mixer) Connect(media MediaType, source HubPort, sink HubPort) error { 34 | req := elem.getInvokeRequest() 35 | 36 | params := make(map[string]interface{}) 37 | 38 | setIfNotEmpty(params, "media", media) 39 | setIfNotEmpty(params, "source", source) 40 | setIfNotEmpty(params, "sink", sink) 41 | 42 | req["params"] = map[string]interface{}{ 43 | "operation": "connect", 44 | "object": elem.Id, 45 | "operationParams": params, 46 | } 47 | 48 | // Call server and wait response 49 | response := <-elem.connection.Request(req) 50 | 51 | // Returns error or nil 52 | return response.Error 53 | 54 | } 55 | 56 | // Disonnects each corresponding :rom:enum:`MediaType` of the given source port 57 | // from the sink port. 58 | func (elem *Mixer) Disconnect(media MediaType, source HubPort, sink HubPort) error { 59 | req := elem.getInvokeRequest() 60 | 61 | params := make(map[string]interface{}) 62 | 63 | setIfNotEmpty(params, "media", media) 64 | setIfNotEmpty(params, "source", source) 65 | setIfNotEmpty(params, "sink", sink) 66 | 67 | req["params"] = map[string]interface{}{ 68 | "operation": "disconnect", 69 | "object": elem.Id, 70 | "operationParams": params, 71 | } 72 | 73 | // Call server and wait response 74 | response := <-elem.connection.Request(req) 75 | 76 | // Returns error or nil 77 | return response.Error 78 | 79 | } 80 | -------------------------------------------------------------------------------- /PlayerEndpoint.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IPlayerEndpoint interface { 6 | Play() error 7 | } 8 | 9 | // Retrieves content from seekable sources in reliable 10 | // mode (does not discard media information) and inject 11 | // them into `KMS`. It 12 | // contains one `MediaSource` for each media type detected. 13 | type PlayerEndpoint struct { 14 | UriEndpoint 15 | } 16 | 17 | // Return contructor params to be called by "Create". 18 | func (elem *PlayerEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 19 | 20 | // Create basic constructor params 21 | ret := map[string]interface{}{ 22 | "mediaPipeline": fmt.Sprintf("%s", from), 23 | "uri": "", 24 | "useEncodedMedia": fmt.Sprintf("%s", from), 25 | } 26 | 27 | // then merge options 28 | mergeOptions(ret, options) 29 | 30 | return ret 31 | 32 | } 33 | 34 | // Starts to send data to the endpoint `MediaSource` 35 | func (elem *PlayerEndpoint) Play() error { 36 | req := elem.getInvokeRequest() 37 | 38 | req["params"] = map[string]interface{}{ 39 | "operation": "play", 40 | "object": elem.Id, 41 | } 42 | 43 | // Call server and wait response 44 | response := <-elem.connection.Request(req) 45 | 46 | // Returns error or nil 47 | return response.Error 48 | 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go Kurento communication package 2 | ================================ 3 | 4 | [Kurento](http://www.kurento.com/) is a WebRTC Media Server built over Gstreamer. It allows stream management between Web browser and Server. 5 | 6 | > WebRTC is a project that provides browser and mobiles integration with Reat Time Communication capabilities via simple APIs. 7 | > -- http://www.webrtc.org/ 8 | 9 | Without Kurento, you will only be able to connect 2 browsers each other. That enougth for a lot of applications but you will need more when you want to stream from one browser to several clients, stream a video to several readers, and so on. For that need, you probably want to have client-server capabilities. 10 | 11 | WebRTC is fully implemented in Kurento Media Server. 12 | 13 | Kurento with Go 14 | --------------- 15 | 16 | With Go at server side, you only need to implement browser to application message system (websocket, xhttpRequest...) to pass information to KMS or to clients. 17 | 18 | Your application will communicate streams information to KMS via Kurento Go Package and to clients. 19 | 20 | See doc at https://godoc.org/github.com/metal3d/kurento-go 21 | 22 | Example: 23 | -------- 24 | 25 | This snippet shows a simple way to have master (sender) and viewer (reciever). Master MUST be created at first. Note that there is no "close" management, this is only to show you how it works. 26 | 27 | Launch KMS server, or if you have docker, run this command: 28 | 29 | docker start --rm -it -p 8888:8888 kurento/media-server-64:5.1.0-trusty 30 | 31 | You will be able to connect to 127.0.0.1:8888 that is the JSONRPC over WebSocket port that opens Kurento. 32 | 33 | In you Go application: 34 | 35 | ```go 36 | import "github.com/metal3d/kurento-go" 37 | 38 | // Create master pipeline and master WebRtcEnpoint 39 | // that will be shared to viewers 40 | var pipeline = new(kurento.MediaPipeline) 41 | var master = new(kurento.WebRtcEndpoint) 42 | var server = kurento.NewConnection("ws://127.0.0.1:8888") 43 | 44 | ... 45 | 46 | // Somewhere 47 | // in a websocket handler: 48 | 49 | message := ReadWebSocket() 50 | 51 | if message["id"] == "master" { 52 | pipeline.Create(master, nil) 53 | answer, _ := master.ProcessOffer(message["sdpOffer"]) 54 | 55 | // need to connect one sink, use self connection 56 | master.Connect(master, "", "", "") // empty options 57 | 58 | // return the answer to sender 59 | SendtoClient(json.Marshal(map[string]string{ 60 | "id" : "masterResponse", 61 | "response" : "accept", 62 | "sdpAnswer" : answer, 63 | })) 64 | } 65 | 66 | if message["id"] == "viewer" { 67 | viewer := new(kurento.WebRtcEndpoint) 68 | pipeline.Create(viewer, nil) 69 | answer, _ := viewer.ProcessOffer(message["sdpOffer"]) 70 | 71 | //connect master to viewer 72 | master.Connect(viewer, "", "", "") 73 | 74 | //return answer to viewer 75 | SendtoClient(json.Marshal(map[string]string{ 76 | "id" : "viewerResponse", 77 | "response" : "accept", 78 | "sdpAnswer" : answer, 79 | })) 80 | } 81 | ``` 82 | 83 | Note that SendToClient() and ReadWebsocket() should be implemented by yourself, you may want to use standard websocket handler, or gorilla websocket. 84 | 85 | The browser side: 86 | 87 | ```javascript 88 | var ws = new WebSocket("ws://127.0.0.1:8000/ws"), 89 | master = null, 90 | viewer = null; 91 | 92 | ws.onmessage = function(resp) { 93 | r = JSON.parse(resp.data); 94 | switch(r.id) { 95 | case "masterResponse": 96 | if (r.response == "accepted") { 97 | master.processSdpAnswer(r.sdpAnswer); 98 | } 99 | break; 100 | case "viewerResponse" : 101 | if (r.response == "accepted") { 102 | viewer.processSdpAnswer(r.sdpAnswer); 103 | } 104 | break; 105 | default: 106 | console.error(r); 107 | break; 108 | } 109 | }; 110 | 111 | ``` 112 | 113 | To launch master: 114 | 115 | ```javascript 116 | function startMaster(){ 117 | var video = $("#mastervideo"); 118 | master = kurentoUtils.WebRtcPeer.startSendOnly(video, function(offer){ 119 | var message = { 120 | id : "master", 121 | sdpOffer : offer 122 | }; 123 | ws.send(JSON.stringify(message)); 124 | console.log("sent offer from master to viewer") 125 | }); 126 | } 127 | 128 | ``` 129 | 130 | And the viewer: 131 | 132 | ```javascript 133 | function startViewer(){ 134 | var video = $("#clientvideo"); 135 | viewer = kurentoUtils.WebRtcPeer.startRecvOnly(video, function(offerSdp) { 136 | var message = { 137 | id : 'viewer', 138 | sdpOffer : offerSdp 139 | }; 140 | ws.send(JSON.stringify(message)); 141 | console.log("sent offer from viewer to master") 142 | }); 143 | } 144 | ``` 145 | 146 | You have to create video element and manage call to startMaster() and startViewer(). 147 | 148 | Remember that the "onclose" process is not implemented on this snippet. 149 | 150 | Help ! 151 | ------ 152 | 153 | I need help to continue the package developpement. This package is generated by https://github.com/metal3d/kurento-go-generator 154 | 155 | Because Kurento provides elements and JSONRPC IDL in json form, it's common to generate the Go Package and not to code by hand the entire source. 156 | 157 | If you want to help, please fork and pull-request on kurento-go-generator repository and **not** on kurento git repository. I will **not** accept pull-request on kurento repository because the code is generated. 158 | 159 | At this time, kurento package is in **alpha** stage. There will be a lot of changes, maybe a rewrite of several parts. Please, forgive me if you have to change a lot of things after an update. 160 | 161 | Soon, package will be released with version and we will use [GoPkg](http://labix.org/gopkg.in) to provide versions. 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /RecorderEndpoint.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IRecorderEndpoint interface { 6 | Record() error 7 | } 8 | 9 | // Provides function to store contents in reliable mode (doesn't discard data). It 10 | // contains `MediaSink` pads for audio and video. 11 | type RecorderEndpoint struct { 12 | UriEndpoint 13 | } 14 | 15 | // Return contructor params to be called by "Create". 16 | func (elem *RecorderEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 17 | 18 | // Create basic constructor params 19 | ret := map[string]interface{}{ 20 | "mediaPipeline": fmt.Sprintf("%s", from), 21 | "uri": "", 22 | "mediaProfile": fmt.Sprintf("%s", from), 23 | "stopOnEndOfStream": fmt.Sprintf("%s", from), 24 | } 25 | 26 | // then merge options 27 | mergeOptions(ret, options) 28 | 29 | return ret 30 | 31 | } 32 | 33 | // Starts storing media received through the `MediaSink` pad 34 | func (elem *RecorderEndpoint) Record() error { 35 | req := elem.getInvokeRequest() 36 | 37 | req["params"] = map[string]interface{}{ 38 | "operation": "record", 39 | "object": elem.Id, 40 | } 41 | 42 | // Call server and wait response 43 | response := <-elem.connection.Request(req) 44 | 45 | // Returns error or nil 46 | return response.Error 47 | 48 | } 49 | -------------------------------------------------------------------------------- /RtpEndpoint.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IRtpEndpoint interface { 6 | } 7 | 8 | // Endpoint that provides bidirectional content delivery capabilities with remote 9 | // networked peers through RTP protocol. An `RtpEndpoint` contains paired sink 10 | // and source `MediaPad` for audio and video. 11 | type RtpEndpoint struct { 12 | SdpEndpoint 13 | } 14 | 15 | // Return contructor params to be called by "Create". 16 | func (elem *RtpEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 17 | 18 | // Create basic constructor params 19 | ret := map[string]interface{}{ 20 | "mediaPipeline": fmt.Sprintf("%s", from), 21 | } 22 | 23 | // then merge options 24 | mergeOptions(ret, options) 25 | 26 | return ret 27 | 28 | } 29 | -------------------------------------------------------------------------------- /WerbrtcEndpoint.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | type IWebRtcEndpoint interface { 6 | GatherCandidates() error 7 | AddIceCandidate(candidate IceCandidate) error 8 | } 9 | 10 | // WebRtcEndpoint interface. This type of "Endpoint" offers media streaming using 11 | // WebRTC. 12 | type WebRtcEndpoint struct { 13 | BaseRtpEndpoint 14 | 15 | // Address of the STUN server (Only IP address are supported) 16 | StunServerAddress string 17 | 18 | // Port of the STUN server 19 | StunServerPort int 20 | } 21 | 22 | // Return contructor params to be called by "Create". 23 | func (elem *WebRtcEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 24 | 25 | // Create basic constructor params 26 | ret := map[string]interface{}{ 27 | "mediaPipeline": fmt.Sprintf("%s", from), 28 | } 29 | 30 | // then merge options 31 | mergeOptions(ret, options) 32 | 33 | return ret 34 | 35 | } 36 | 37 | // Init the gathering of ICE candidates. 38 | // It must be called after SdpEndpoint::generateOffer or SdpEndpoint::processOffer 39 | func (elem *WebRtcEndpoint) GatherCandidates() error { 40 | req := elem.getInvokeRequest() 41 | 42 | req["params"] = map[string]interface{}{ 43 | "operation": "gatherCandidates", 44 | "object": elem.Id, 45 | } 46 | 47 | // Call server and wait response 48 | response := <-elem.connection.Request(req) 49 | 50 | // Returns error or nil 51 | return response.Error 52 | 53 | } 54 | 55 | // Provide a remote ICE candidate 56 | func (elem *WebRtcEndpoint) AddIceCandidate(candidate IceCandidate) error { 57 | req := elem.getInvokeRequest() 58 | 59 | params := make(map[string]interface{}) 60 | 61 | setIfNotEmpty(params, "candidate", candidate) 62 | 63 | req["params"] = map[string]interface{}{ 64 | "operation": "addIceCandidate", 65 | "object": elem.Id, 66 | "operationParams": params, 67 | } 68 | 69 | // Call server and wait response 70 | response := <-elem.connection.Request(req) 71 | 72 | // Returns error or nil 73 | return response.Error 74 | 75 | } 76 | -------------------------------------------------------------------------------- /base.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | var debug = false 11 | 12 | // Debug activate debug information. 13 | func Debug(state bool) { 14 | debug = state 15 | } 16 | 17 | // IMadiaElement implements some basic methods as getConstructorParams or Create(). 18 | type IMediaObject interface { 19 | 20 | // Return the constructor parameters 21 | getConstructorParams(IMediaObject, map[string]interface{}) map[string]interface{} 22 | 23 | // Each media object should be able to create another object 24 | // Those options are sent to getConstructorParams 25 | Create(IMediaObject, map[string]interface{}) 26 | 27 | // Set ID of the element 28 | setId(string) 29 | 30 | //Implement Stringer 31 | String() string 32 | 33 | setParent(IMediaObject) 34 | addChild(IMediaObject) 35 | 36 | setConnection(*Connection) 37 | } 38 | 39 | // Create object "m" with given "options" 40 | func (elem *MediaObject) Create(m IMediaObject, options map[string]interface{}) { 41 | req := elem.getCreateRequest() 42 | constparams := m.getConstructorParams(elem, options) 43 | // TODO params["sessionId"] 44 | req["params"] = map[string]interface{}{ 45 | "type": getMediaElementType(m), 46 | "constructorParams": constparams, 47 | } 48 | if debug { 49 | log.Printf("request to be sent: %+v\n", req) 50 | } 51 | 52 | m.setConnection(elem.connection) 53 | 54 | res := <-elem.connection.Request(req) 55 | 56 | if debug { 57 | log.Println("Oncreate response: ", res) 58 | } 59 | 60 | if res.Result["value"] != "" { 61 | elem.addChild(m) 62 | //m.setParent(elem) 63 | m.setId(res.Result["value"]) 64 | } 65 | } 66 | 67 | // Implement setConnection that allows element to handle connection 68 | func (elem *MediaObject) setConnection(c *Connection) { 69 | elem.connection = c 70 | } 71 | 72 | // Set parent of current element 73 | // BUG(recursion) a recursion happends while testing, I must find why 74 | func (elem *MediaObject) setParent(m IMediaObject) { 75 | elem.Parent = m 76 | } 77 | 78 | // Append child to the element 79 | func (elem *MediaObject) addChild(m IMediaObject) { 80 | elem.Childs = append(elem.Childs, m) 81 | } 82 | 83 | // setId set object id from a KMS response 84 | func (m *MediaObject) setId(id string) { 85 | m.Id = id 86 | } 87 | 88 | // Build a prepared create request 89 | func (m *MediaObject) getCreateRequest() map[string]interface{} { 90 | 91 | return map[string]interface{}{ 92 | "jsonrpc": "2.0", 93 | "method": "create", 94 | "params": make(map[string]interface{}), 95 | } 96 | } 97 | 98 | // Build a prepared invoke request 99 | func (m *MediaObject) getInvokeRequest() map[string]interface{} { 100 | req := m.getCreateRequest() 101 | req["method"] = "invoke" 102 | 103 | return req 104 | } 105 | 106 | // String implements fmt.Stringer interface, return ID 107 | func (m *MediaObject) String() string { 108 | return m.Id 109 | } 110 | 111 | // Return name of the object 112 | func getMediaElementType(i interface{}) string { 113 | n := reflect.TypeOf(i).String() 114 | p := strings.Split(n, ".") 115 | return p[len(p)-1] 116 | } 117 | 118 | func mergeOptions(a, b map[string]interface{}) { 119 | for key, val := range b { 120 | a[key] = val 121 | } 122 | } 123 | 124 | func setIfNotEmpty(param map[string]interface{}, name string, t interface{}) { 125 | 126 | switch v := t.(type) { 127 | case string: 128 | if v != "" { 129 | param[name] = v 130 | } 131 | case int, float64: 132 | if v != 0 { 133 | param[name] = v 134 | } 135 | case bool: 136 | if v { 137 | param[name] = v 138 | } 139 | case IMediaObject, fmt.Stringer: 140 | if v != nil { 141 | val := fmt.Sprintf("%s", v) 142 | if val != "" { 143 | param[name] = val 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /complexTypes.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | // Media Profile. 4 | // Currently WEBM and MP4 are supported. 5 | type MediaProfileSpecType string 6 | 7 | // Implement fmt.Stringer interface 8 | func (t MediaProfileSpecType) String() string { 9 | return string(t) 10 | } 11 | 12 | const ( 13 | MEDIAPROFILESPECTYPE_WEBM MediaProfileSpecType = "WEBM" 14 | MEDIAPROFILESPECTYPE_MP4 MediaProfileSpecType = "MP4" 15 | MEDIAPROFILESPECTYPE_WEBM_VIDEO_ONLY MediaProfileSpecType = "WEBM_VIDEO_ONLY" 16 | MEDIAPROFILESPECTYPE_WEBM_AUDIO_ONLY MediaProfileSpecType = "WEBM_AUDIO_ONLY" 17 | MEDIAPROFILESPECTYPE_MP4_VIDEO_ONLY MediaProfileSpecType = "MP4_VIDEO_ONLY" 18 | MEDIAPROFILESPECTYPE_MP4_AUDIO_ONLY MediaProfileSpecType = "MP4_AUDIO_ONLY" 19 | ) 20 | 21 | type IceCandidate struct { 22 | Candidate string 23 | SdpMid string 24 | SdpMLineIndex int 25 | } 26 | 27 | type ServerInfo struct { 28 | Version string 29 | Modules []ModuleInfo 30 | Type ServerType 31 | Capabilities []string 32 | } 33 | 34 | // Indicates if the server is a real media server or a proxy 35 | type ServerType string 36 | 37 | // Implement fmt.Stringer interface 38 | func (t ServerType) String() string { 39 | return string(t) 40 | } 41 | 42 | const ( 43 | SERVERTYPE_KMS ServerType = "KMS" 44 | SERVERTYPE_KCS ServerType = "KCS" 45 | ) 46 | 47 | type ModuleInfo struct { 48 | Version string 49 | Name string 50 | Factories []string 51 | } 52 | 53 | // Type of media stream to be exchanged. 54 | // Can take the values AUDIO, DATA or VIDEO. 55 | type MediaType string 56 | 57 | // Implement fmt.Stringer interface 58 | func (t MediaType) String() string { 59 | return string(t) 60 | } 61 | 62 | const ( 63 | MEDIATYPE_AUDIO MediaType = "AUDIO" 64 | MEDIATYPE_DATA MediaType = "DATA" 65 | MEDIATYPE_VIDEO MediaType = "VIDEO" 66 | ) 67 | 68 | // Type of filter to be created. 69 | // Can take the values AUDIO, VIDEO or AUTODETECT. 70 | type FilterType string 71 | 72 | // Implement fmt.Stringer interface 73 | func (t FilterType) String() string { 74 | return string(t) 75 | } 76 | 77 | const ( 78 | FILTERTYPE_AUDIO FilterType = "AUDIO" 79 | FILTERTYPE_AUTODETECT FilterType = "AUTODETECT" 80 | FILTERTYPE_VIDEO FilterType = "VIDEO" 81 | ) 82 | 83 | // Codec used for transmission of video. 84 | type VideoCodec string 85 | 86 | // Implement fmt.Stringer interface 87 | func (t VideoCodec) String() string { 88 | return string(t) 89 | } 90 | 91 | const ( 92 | VIDEOCODEC_VP8 VideoCodec = "VP8" 93 | VIDEOCODEC_H264 VideoCodec = "H264" 94 | VIDEOCODEC_RAW VideoCodec = "RAW" 95 | ) 96 | 97 | // Codec used for transmission of audio. 98 | type AudioCodec string 99 | 100 | // Implement fmt.Stringer interface 101 | func (t AudioCodec) String() string { 102 | return string(t) 103 | } 104 | 105 | const ( 106 | AUDIOCODEC_OPUS AudioCodec = "OPUS" 107 | AUDIOCODEC_PCMU AudioCodec = "PCMU" 108 | AUDIOCODEC_RAW AudioCodec = "RAW" 109 | ) 110 | 111 | type Fraction struct { 112 | Numerator int 113 | Denominator int 114 | } 115 | 116 | type AudioCaps struct { 117 | Codec AudioCodec 118 | Bitrate int 119 | } 120 | 121 | type VideoCaps struct { 122 | Codec VideoCodec 123 | Framerate Fraction 124 | } 125 | 126 | type ElementConnectionData struct { 127 | Source MediaElement 128 | Sink MediaElement 129 | Type MediaType 130 | SourceDescription string 131 | SinkDescription string 132 | } 133 | -------------------------------------------------------------------------------- /core.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import "fmt" 4 | 5 | // Base for all objects that can be created in the media server. 6 | type MediaObject struct { 7 | connection *Connection 8 | 9 | // `MediaPipeline` to which this MediaObject belong, or the pipeline itself if 10 | // invoked over a `MediaPipeline` 11 | MediaPipeline IMediaPipeline 12 | 13 | // parent of this media object. The type of the parent depends on the type of the 14 | // element. The parent of a `MediaPad` is its `MediaElement`; the parent of a 15 | // `Hub` or a `MediaElement` is its `MediaPipeline`. A `MediaPipeline` has no 16 | // parent, i.e. the property is null 17 | Parent IMediaObject 18 | 19 | // unique identifier of the mediaobject. 20 | Id string 21 | 22 | // Childs of current object, all returned objects have parent set to current 23 | // object 24 | Childs []IMediaObject 25 | 26 | // Object name. This is just a comodity to simplify developers life debugging, it 27 | // is not used internally for indexing nor idenfiying the objects. By default is 28 | // the object type followed by the object id. 29 | Name string 30 | } 31 | 32 | // Return contructor params to be called by "Create". 33 | func (elem *MediaObject) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 34 | return options 35 | 36 | } 37 | 38 | type IServerManager interface { 39 | } 40 | 41 | // This is a standalone object for managing the MediaServer 42 | type ServerManager struct { 43 | MediaObject 44 | 45 | // Server information, version, modules, factories, etc 46 | Info *ServerInfo 47 | 48 | // All the pipelines available in the server 49 | Pipelines []IMediaPipeline 50 | 51 | // All active sessions in the server 52 | Sessions []string 53 | } 54 | 55 | // Return contructor params to be called by "Create". 56 | func (elem *ServerManager) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 57 | return options 58 | 59 | } 60 | 61 | type ISessionEndpoint interface { 62 | } 63 | 64 | // Session based endpoint. A session is considered to be started when the media 65 | // exchange starts. On the other hand, sessions terminate when a timeout, 66 | // defined by the developer, takes place after the connection is lost. 67 | type SessionEndpoint struct { 68 | Endpoint 69 | } 70 | 71 | // Return contructor params to be called by "Create". 72 | func (elem *SessionEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 73 | return options 74 | 75 | } 76 | 77 | type IHub interface { 78 | } 79 | 80 | // A Hub is a routing `MediaObject`. It connects several `endpoints ` 81 | // together 82 | type Hub struct { 83 | MediaObject 84 | } 85 | 86 | // Return contructor params to be called by "Create". 87 | func (elem *Hub) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 88 | return options 89 | 90 | } 91 | 92 | type IFilter interface { 93 | } 94 | 95 | // Base interface for all filters. This is a certain type of `MediaElement`, that 96 | // processes media injected through its sinks, and delivers the outcome through 97 | // its sources. 98 | type Filter struct { 99 | MediaElement 100 | } 101 | 102 | // Return contructor params to be called by "Create". 103 | func (elem *Filter) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 104 | return options 105 | 106 | } 107 | 108 | type IEndpoint interface { 109 | } 110 | 111 | // Base interface for all end points. An Endpoint is a `MediaElement` 112 | // that allow `KMS` to interchange media contents with external systems, 113 | // supporting different transport protocols and mechanisms, such as `RTP`, 114 | // `WebRTC`, `HTTP`, "file:/" URLs... An "Endpoint" may 115 | // contain both sources and sinks for different media types, to provide 116 | // bidirectional communication. 117 | type Endpoint struct { 118 | MediaElement 119 | } 120 | 121 | // Return contructor params to be called by "Create". 122 | func (elem *Endpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 123 | return options 124 | 125 | } 126 | 127 | type IHubPort interface { 128 | } 129 | 130 | // This `MediaElement` specifies a connection with a `Hub` 131 | type HubPort struct { 132 | MediaElement 133 | } 134 | 135 | // Return contructor params to be called by "Create". 136 | func (elem *HubPort) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 137 | 138 | // Create basic constructor params 139 | ret := map[string]interface{}{ 140 | "hub": fmt.Sprintf("%s", from), 141 | } 142 | 143 | // then merge options 144 | mergeOptions(ret, options) 145 | 146 | return ret 147 | 148 | } 149 | 150 | type IPassThrough interface { 151 | } 152 | 153 | // This `MediaElement` that just passes media through 154 | type PassThrough struct { 155 | MediaElement 156 | } 157 | 158 | // Return contructor params to be called by "Create". 159 | func (elem *PassThrough) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 160 | 161 | // Create basic constructor params 162 | ret := map[string]interface{}{ 163 | "mediaPipeline": fmt.Sprintf("%s", from), 164 | } 165 | 166 | // then merge options 167 | mergeOptions(ret, options) 168 | 169 | return ret 170 | 171 | } 172 | 173 | type IUriEndpoint interface { 174 | Pause() error 175 | Stop() error 176 | } 177 | 178 | // Interface for endpoints the require a URI to work. An example of this, would be 179 | // a `PlayerEndpoint` whose URI property could be used to locate a file to stream 180 | type UriEndpoint struct { 181 | Endpoint 182 | 183 | // The uri for this endpoint. 184 | Uri string 185 | } 186 | 187 | // Return contructor params to be called by "Create". 188 | func (elem *UriEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 189 | return options 190 | 191 | } 192 | 193 | // Pauses the feed 194 | func (elem *UriEndpoint) Pause() error { 195 | req := elem.getInvokeRequest() 196 | 197 | req["params"] = map[string]interface{}{ 198 | "operation": "pause", 199 | "object": elem.Id, 200 | } 201 | 202 | // Call server and wait response 203 | response := <-elem.connection.Request(req) 204 | 205 | // Returns error or nil 206 | return response.Error 207 | 208 | } 209 | 210 | // Stops the feed 211 | func (elem *UriEndpoint) Stop() error { 212 | req := elem.getInvokeRequest() 213 | 214 | req["params"] = map[string]interface{}{ 215 | "operation": "stop", 216 | "object": elem.Id, 217 | } 218 | 219 | // Call server and wait response 220 | response := <-elem.connection.Request(req) 221 | 222 | // Returns error or nil 223 | return response.Error 224 | 225 | } 226 | 227 | type IMediaPipeline interface { 228 | } 229 | 230 | // A pipeline is a container for a collection of `MediaElements` and 231 | // `MediaMixers`. It offers the methods needed to control the 232 | // creation and connection of elements inside a certain pipeline. 233 | type MediaPipeline struct { 234 | MediaObject 235 | } 236 | 237 | // Return contructor params to be called by "Create". 238 | func (elem *MediaPipeline) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 239 | return options 240 | 241 | } 242 | 243 | type ISdpEndpoint interface { 244 | GenerateOffer() (string, error) 245 | ProcessOffer(offer string) (string, error) 246 | ProcessAnswer(answer string) (string, error) 247 | GetLocalSessionDescriptor() (string, error) 248 | GetRemoteSessionDescriptor() (string, error) 249 | } 250 | 251 | // Implements an SDP negotiation endpoint able to generate and process 252 | // offers/responses and that configures resources according to 253 | // negotiated Session Description 254 | type SdpEndpoint struct { 255 | SessionEndpoint 256 | 257 | // Maximum video bandwidth for receiving. 258 | // Unit: kbps(kilobits per second). 259 | // 0: unlimited. 260 | // Default value: 500 261 | MaxVideoRecvBandwidth int 262 | } 263 | 264 | // Return contructor params to be called by "Create". 265 | func (elem *SdpEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 266 | return options 267 | 268 | } 269 | 270 | // Request a SessionSpec offer. 271 | // This can be used to initiate a connection. 272 | // Returns: 273 | // // The SDP offer. 274 | func (elem *SdpEndpoint) GenerateOffer() (string, error) { 275 | req := elem.getInvokeRequest() 276 | 277 | req["params"] = map[string]interface{}{ 278 | "operation": "generateOffer", 279 | "object": elem.Id, 280 | } 281 | 282 | // Call server and wait response 283 | response := <-elem.connection.Request(req) 284 | 285 | // // The SDP offer. 286 | 287 | return response.Result["value"], response.Error 288 | 289 | } 290 | 291 | // Request the NetworkConnection to process the given SessionSpec offer (from the 292 | // remote User Agent) 293 | // Returns: 294 | // // The chosen configuration from the ones stated in the SDP offer 295 | func (elem *SdpEndpoint) ProcessOffer(offer string) (string, error) { 296 | req := elem.getInvokeRequest() 297 | 298 | params := make(map[string]interface{}) 299 | 300 | setIfNotEmpty(params, "offer", offer) 301 | 302 | req["params"] = map[string]interface{}{ 303 | "operation": "processOffer", 304 | "object": elem.Id, 305 | "operationParams": params, 306 | } 307 | 308 | // Call server and wait response 309 | response := <-elem.connection.Request(req) 310 | 311 | // // The chosen configuration from the ones stated in the SDP offer 312 | 313 | return response.Result["value"], response.Error 314 | 315 | } 316 | 317 | // Request the NetworkConnection to process the given SessionSpec answer (from the 318 | // remote User Agent). 319 | // Returns: 320 | // // Updated SDP offer, based on the answer received. 321 | func (elem *SdpEndpoint) ProcessAnswer(answer string) (string, error) { 322 | req := elem.getInvokeRequest() 323 | 324 | params := make(map[string]interface{}) 325 | 326 | setIfNotEmpty(params, "answer", answer) 327 | 328 | req["params"] = map[string]interface{}{ 329 | "operation": "processAnswer", 330 | "object": elem.Id, 331 | "operationParams": params, 332 | } 333 | 334 | // Call server and wait response 335 | response := <-elem.connection.Request(req) 336 | 337 | // // Updated SDP offer, based on the answer received. 338 | 339 | return response.Result["value"], response.Error 340 | 341 | } 342 | 343 | // This method gives access to the SessionSpec offered by this NetworkConnection. 344 | // .. note:: This method returns the local MediaSpec, negotiated or not. If no 345 | // offer has been generated yet, it returns null. It an offer has been 346 | // generated it returns the offer and if an answer has been processed 347 | // it returns the negotiated local SessionSpec. 348 | // Returns: 349 | // // The last agreed SessionSpec 350 | func (elem *SdpEndpoint) GetLocalSessionDescriptor() (string, error) { 351 | req := elem.getInvokeRequest() 352 | 353 | req["params"] = map[string]interface{}{ 354 | "operation": "getLocalSessionDescriptor", 355 | "object": elem.Id, 356 | } 357 | 358 | // Call server and wait response 359 | response := <-elem.connection.Request(req) 360 | 361 | // // The last agreed SessionSpec 362 | 363 | return response.Result["value"], response.Error 364 | 365 | } 366 | 367 | // This method gives access to the remote session description. 368 | // .. note:: This method returns the media previously agreed after a complete 369 | // offer-answer exchange. If no media has been agreed yet, it returns null. 370 | // Returns: 371 | // // The last agreed User Agent session description 372 | func (elem *SdpEndpoint) GetRemoteSessionDescriptor() (string, error) { 373 | req := elem.getInvokeRequest() 374 | 375 | req["params"] = map[string]interface{}{ 376 | "operation": "getRemoteSessionDescriptor", 377 | "object": elem.Id, 378 | } 379 | 380 | // Call server and wait response 381 | response := <-elem.connection.Request(req) 382 | 383 | // // The last agreed User Agent session description 384 | 385 | return response.Result["value"], response.Error 386 | 387 | } 388 | 389 | type IBaseRtpEndpoint interface { 390 | } 391 | 392 | // Base class to manage common RTP features. 393 | type BaseRtpEndpoint struct { 394 | SdpEndpoint 395 | 396 | // Minimum video bandwidth for sending. 397 | // Unit: kbps(kilobits per second). 398 | // 0: unlimited. 399 | // Default value: 100 400 | MinVideoSendBandwidth int 401 | 402 | // Maximum video bandwidth for sending. 403 | // Unit: kbps(kilobits per second). 404 | // 0: unlimited. 405 | // Default value: 500 406 | MaxVideoSendBandwidth int 407 | } 408 | 409 | // Return contructor params to be called by "Create". 410 | func (elem *BaseRtpEndpoint) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 411 | return options 412 | 413 | } 414 | 415 | type IMediaElement interface { 416 | GetSourceConnections(mediaType MediaType, description string) ([]ElementConnectionData, error) 417 | GetSinkConnections(mediaType MediaType, description string) ([]ElementConnectionData, error) 418 | Connect(sink IMediaElement, mediaType MediaType, sourceMediaDescription string, sinkMediaDescription string) error 419 | Disconnect(sink IMediaElement, mediaType MediaType, sourceMediaDescription string, sinkMediaDescription string) error 420 | SetAudioFormat(caps AudioCaps) error 421 | SetVideoFormat(caps VideoCaps) error 422 | } 423 | 424 | // Basic building blocks of the media server, that can be interconnected through 425 | // the API. A `MediaElement` is a module that encapsulates a specific media 426 | // capability. They can be connected to create media pipelines where those 427 | // capabilities are applied, in sequence, to the stream going through the 428 | // pipeline. 429 | // `MediaElement` objects are classified by its supported media type (audio, 430 | // video, etc.) 431 | type MediaElement struct { 432 | MediaObject 433 | } 434 | 435 | // Return contructor params to be called by "Create". 436 | func (elem *MediaElement) getConstructorParams(from IMediaObject, options map[string]interface{}) map[string]interface{} { 437 | return options 438 | 439 | } 440 | 441 | // Get the connections information of the elements that are sending media to this 442 | // element `MediaElement` 443 | // Returns: 444 | // // A list of the connections information that are sending media to this 445 | // element. 446 | // // The list will be empty if no sources are found. 447 | func (elem *MediaElement) GetSourceConnections(mediaType MediaType, description string) ([]ElementConnectionData, error) { 448 | req := elem.getInvokeRequest() 449 | 450 | params := make(map[string]interface{}) 451 | 452 | setIfNotEmpty(params, "mediaType", mediaType) 453 | setIfNotEmpty(params, "description", description) 454 | 455 | req["params"] = map[string]interface{}{ 456 | "operation": "getSourceConnections", 457 | "object": elem.Id, 458 | "operationParams": params, 459 | } 460 | 461 | // Call server and wait response 462 | response := <-elem.connection.Request(req) 463 | 464 | // // A list of the connections information that are sending media to this 465 | // element. 466 | // // The list will be empty if no sources are found. 467 | 468 | ret := []ElementConnectionData{} 469 | return ret, response.Error 470 | 471 | } 472 | 473 | // Returns a list of the connections information of the elements that ere 474 | // receiving media from this element. 475 | // Returns: 476 | // // A list of the connections information that arereceiving media from this 477 | // // element. The list will be empty if no sinks are found. 478 | func (elem *MediaElement) GetSinkConnections(mediaType MediaType, description string) ([]ElementConnectionData, error) { 479 | req := elem.getInvokeRequest() 480 | 481 | params := make(map[string]interface{}) 482 | 483 | setIfNotEmpty(params, "mediaType", mediaType) 484 | setIfNotEmpty(params, "description", description) 485 | 486 | req["params"] = map[string]interface{}{ 487 | "operation": "getSinkConnections", 488 | "object": elem.Id, 489 | "operationParams": params, 490 | } 491 | 492 | // Call server and wait response 493 | response := <-elem.connection.Request(req) 494 | 495 | // // A list of the connections information that arereceiving media from this 496 | // // element. The list will be empty if no sinks are found. 497 | 498 | ret := []ElementConnectionData{} 499 | return ret, response.Error 500 | 501 | } 502 | 503 | // Connects two elements, with the given restrictions, current `MediaElement` will 504 | // start emmit media to sink element. Connection could take place in the future, 505 | // when both media element show capabilities for connecting with the given 506 | // restrictions 507 | func (elem *MediaElement) Connect(sink IMediaElement, mediaType MediaType, sourceMediaDescription string, sinkMediaDescription string) error { 508 | req := elem.getInvokeRequest() 509 | 510 | params := make(map[string]interface{}) 511 | 512 | setIfNotEmpty(params, "sink", sink) 513 | setIfNotEmpty(params, "mediaType", mediaType) 514 | setIfNotEmpty(params, "sourceMediaDescription", sourceMediaDescription) 515 | setIfNotEmpty(params, "sinkMediaDescription", sinkMediaDescription) 516 | 517 | req["params"] = map[string]interface{}{ 518 | "operation": "connect", 519 | "object": elem.Id, 520 | "operationParams": params, 521 | } 522 | 523 | // Call server and wait response 524 | response := <-elem.connection.Request(req) 525 | 526 | // Returns error or nil 527 | return response.Error 528 | 529 | } 530 | 531 | // Disconnects two elements, with the given restrictions, current `MediaElement` 532 | // stops sending media to sink element. If the previously requested connection 533 | // didn't took place it is also removed 534 | func (elem *MediaElement) Disconnect(sink IMediaElement, mediaType MediaType, sourceMediaDescription string, sinkMediaDescription string) error { 535 | req := elem.getInvokeRequest() 536 | 537 | params := make(map[string]interface{}) 538 | 539 | setIfNotEmpty(params, "sink", sink) 540 | setIfNotEmpty(params, "mediaType", mediaType) 541 | setIfNotEmpty(params, "sourceMediaDescription", sourceMediaDescription) 542 | setIfNotEmpty(params, "sinkMediaDescription", sinkMediaDescription) 543 | 544 | req["params"] = map[string]interface{}{ 545 | "operation": "disconnect", 546 | "object": elem.Id, 547 | "operationParams": params, 548 | } 549 | 550 | // Call server and wait response 551 | response := <-elem.connection.Request(req) 552 | 553 | // Returns error or nil 554 | return response.Error 555 | 556 | } 557 | 558 | // Sets the type of data for the audio stream. MediaElements that do not support 559 | // configuration of audio capabilities will raise an exception 560 | func (elem *MediaElement) SetAudioFormat(caps AudioCaps) error { 561 | req := elem.getInvokeRequest() 562 | 563 | params := make(map[string]interface{}) 564 | 565 | setIfNotEmpty(params, "caps", caps) 566 | 567 | req["params"] = map[string]interface{}{ 568 | "operation": "setAudioFormat", 569 | "object": elem.Id, 570 | "operationParams": params, 571 | } 572 | 573 | // Call server and wait response 574 | response := <-elem.connection.Request(req) 575 | 576 | // Returns error or nil 577 | return response.Error 578 | 579 | } 580 | 581 | // Sets the type of data for the video stream. MediaElements that do not support 582 | // configuration of video capabilities will raise an exception 583 | func (elem *MediaElement) SetVideoFormat(caps VideoCaps) error { 584 | req := elem.getInvokeRequest() 585 | 586 | params := make(map[string]interface{}) 587 | 588 | setIfNotEmpty(params, "caps", caps) 589 | 590 | req["params"] = map[string]interface{}{ 591 | "operation": "setVideoFormat", 592 | "object": elem.Id, 593 | "operationParams": params, 594 | } 595 | 596 | // Call server and wait response 597 | response := <-elem.connection.Request(req) 598 | 599 | // Returns error or nil 600 | return response.Error 601 | 602 | } 603 | -------------------------------------------------------------------------------- /websocket.go: -------------------------------------------------------------------------------- 1 | package kurento 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | 8 | "golang.org/x/net/websocket" 9 | ) 10 | 11 | // Error that can be filled in response 12 | type Error struct { 13 | Code int64 14 | Message string 15 | Data string 16 | } 17 | 18 | // Implements error built-in interface 19 | func (e *Error) Error() string { 20 | return fmt.Sprintf("[%d] %s %s", e.Code, e.Message, e.Data) 21 | } 22 | 23 | // Response represents server response 24 | type Response struct { 25 | Jsonrpc string 26 | Id float64 27 | Result map[string]string // should change if result has no several form 28 | Error *Error 29 | } 30 | 31 | type Connection struct { 32 | clientId float64 33 | clients map[float64]chan Response 34 | host string 35 | ws *websocket.Conn 36 | SessionId string 37 | } 38 | 39 | var connections = make(map[string]*Connection) 40 | 41 | func NewConnection(host string) *Connection { 42 | if connections[host] != nil { 43 | return connections[host] 44 | } 45 | 46 | c := new(Connection) 47 | connections[host] = c 48 | 49 | c.clients = make(map[float64]chan Response) 50 | var err error 51 | c.ws, err = websocket.Dial(host+"/kurento", "", "http://127.0.0.1") 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | c.host = host 56 | go c.handleResponse() 57 | return c 58 | } 59 | 60 | func (c *Connection) Create(m IMediaObject, options map[string]interface{}) { 61 | elem := &MediaObject{} 62 | elem.setConnection(c) 63 | elem.Create(m, options) 64 | } 65 | 66 | func (c *Connection) handleResponse() { 67 | for { // run forever 68 | r := Response{} 69 | websocket.JSON.Receive(c.ws, &r) 70 | if r.Result["sessionId"] != "" { 71 | if debug { 72 | log.Println("SESSIONID RETURNED") 73 | } 74 | c.SessionId = r.Result["sessionId"] 75 | } 76 | // if webscocket client exists, send response to the chanel 77 | if c.clients[r.Id] != nil { 78 | c.clients[r.Id] <- r 79 | // chanel is read, we can delete it 80 | delete(c.clients, r.Id) 81 | } else if debug { 82 | log.Println("Dropped message because there is no client ", r.Id) 83 | log.Println(r) 84 | } 85 | 86 | } 87 | } 88 | 89 | func (c *Connection) Request(req map[string]interface{}) <-chan Response { 90 | c.clientId++ 91 | req["id"] = c.clientId 92 | if c.SessionId != "" { 93 | req["sesionId"] = c.SessionId 94 | } 95 | c.clients[c.clientId] = make(chan Response) 96 | if debug { 97 | j, _ := json.MarshalIndent(req, "", " ") 98 | log.Println("json", string(j)) 99 | } 100 | websocket.JSON.Send(c.ws, req) 101 | return c.clients[c.clientId] 102 | } 103 | --------------------------------------------------------------------------------