# Push Publishing

OvenMediaEngine Enterprise는 Live Stream을 다른 System으로 Re-streaming할 수 있는 Push Publishing 기능을 제공합니다. 지원 Protocol은 널리 사용되는 SRT, RTMP, MPEG-2 TS입니다.

OvenMediaEngine의 `StreamMap` 기능을 활용하여 사전 정의된 조건에 따라 Re-streaming을 자동으로 수행할 수 있습니다. 또한, REST API을 통한 Controling 및 Monitoring을 지원합니다.

## Push Publishing 설정하기

### Push Publisher

Push Publishing을 사용하려면 `Server.xml` 내 `<Applications><Publishers>`에서 `<Push>`를 설정해야합니다.

```xml
<Applications>
  <Application>
     ...
    <Publishers>
      ... 
      <Push>
         <!-- [Optional] -->
         <StreamMap>
           <Enable>true</Enable>
           <Path>push_info.xml</Path>
         </StreamMap>
         <!-- Connection Timeout -->
         <ConnectionTimeout>1000</ConnectionTimeout>
         <!-- Send Timeout -->
         <SendTimeout>100</SendTimeout>
      </Push>
      ...
    </Publishers>
  </Application>
</Applications>
```

{% hint style="info" %}
RTMP Protocol은 H264 (Video Codec)와 AAC (Audio Codec)만 지원합니다.
{% endhint %}

OvenMediaEngine Enterprise 0.20.2.1-1 버전부터 Push Publisher에서 `ConnectionTimeout`과 `SendTimeout` 옵션을 사용할 수 있습니다.

<table><thead><tr><th width="249.5555419921875">Element</th><th>Description</th></tr></thead><tbody><tr><td><code>ConnectionTimeout</code></td><td><p>Server와의 연결을 시도할 때 허용되는 최대 대기 시간입니다.</p><ul><li>단위: ms</li></ul></td></tr><tr><td><code>SendTimeout</code></td><td><p>연결된 상태에서 Data를 전송할 때 허용되는 최대 대기 시간입니다.</p><ul><li>단위: ms</li></ul></td></tr></tbody></table>

### StreamMap

`<StreamMap>`은 사용자 정의 조건에 따라 콘텐츠를 자동으로 Push Publishing하기 위해 사용합니다. XML File Path는 `<ApplicationPath>/conf`를 기준으로 한 상대 경로를 지정해야 합니다.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<PushInfo>
  <Push>
    <Enable>true</Enable>
    <StreamName>stream_a_*</StreamName>
    <VariantNames>video_h264,audio_aac</VariantNames>
    <Protocol>rtmp</Protocol>
    <Url>rtmp://1.2.3.4:1935/app/${SourceStream}</Url>
    <StreamKey></StreamKey>
    <TimestampMode>ZeroBased</TimestampMode>
  </Push>
  <Push>
    <Enable>true</Enable>
    <StreamName>stream_b_*</StreamName>
    <VariantNames></VariantNames>
    <Protocol>srt</Protocol>
    <Url>srt://1.2.3.4:9999?streamid=srt%3A%2F%2F1.2.3.4%3A9999%2Fapp%2Fstream</Url>
    <TimestampMode>Original</TimestampMode>
  </Push>
  <Push>
    <Enable>false</Enable>
    <StreamName>stream_c_*</StreamName>
    <VariantNames></VariantNames>
    <Protocol>mpegts</Protocol>
    <Url>udp://1.2.3.4:2400</Url>
  </Push>
</PushInfo>
```

#### XML Elements

<table><thead><tr><th width="189.5555419921875">Element</th><th width="137.8887939453125" align="center">Requirement</th><th>Description</th></tr></thead><tbody><tr><td><code>&#x3C;StreamName></code></td><td align="center">Required</td><td><p>Output Stream을 매칭하는 데 사용합니다.</p><ul><li>Wildcard characters (<code>*</code>)를 지원합니다.</li></ul></td></tr><tr><td><code>&#x3C;VariantNames></code></td><td align="center">Required</td><td><p>특정 Track을 선택할 때 사용합니다. <code>&#x3C;VariantNames></code>를 지정하지 않으면 기본적으로 모든 Track이 전송됩니다.</p><ul><li>여러 Variant는 콤마(<code>,</code>)로 구분하여 지정할 수 있습니다.</li></ul></td></tr><tr><td><code>&#x3C;Protocol></code></td><td align="center">Required</td><td><p>지원 Protocol은 <code>srt</code>, <code>mpegts</code>, <code>rtmp</code>입니다.</p><ul><li><code>SRT</code> 또는 <code>MPEG-TS</code> 전송 시, Network 및 환경(예: Firewall, Port, Packet Loss 등)을 고려해야 합니다.</li><li><code>RTMP</code> 전송 시, H.264 + AAC 사용을 권장합니다.</li></ul></td></tr><tr><td><code>&#x3C;Url></code></td><td align="center">Required</td><td><p>Destination Address를 지정합니다.</p><ul><li>Macro를 지원합니다.</li></ul></td></tr><tr><td><code>&#x3C;StreamKey></code></td><td align="center">Optional</td><td><p>필요 시 Stream Key를 지정합니다 (<code>RTMP</code>). </p><ul><li>Macro를 지원합니다.</li></ul></td></tr><tr><td><code>&#x3C;TimestampMode></code></td><td align="center"><p>Optional</p><p><sub><em><mark style="color:yellow;">(Enterprise Only)</mark></em></sub></p></td><td><p>Outgoing Packet의 Timestamp Mode를 설정합니다.</p><ul><li><code>ZeroBased</code>: 전송 시작 시점부터 Timestamp를 0으로 설정하여 전달합니다.</li><li> <code>Original</code> (default): 원본 Stream과 동일한 Timestamp를 전달합니다.</li></ul></td></tr></tbody></table>

#### Macros

자동화 및 운영 효율 향상을 위해 Macro 활용을 권장합니다.

<table><thead><tr><th width="249.5555419921875">Macro</th><th>Description</th></tr></thead><tbody><tr><td>${Application}</td><td>Application name</td></tr><tr><td>${SourceStream}</td><td>Source stream name</td></tr><tr><td>${Stream}</td><td>Output stream name</td></tr></tbody></table>

## API Interface

### Push Publishing 시작하기

> **Request**

<details>

<summary><mark style="color:blue;">POST</mark> /v1/vhosts/{vhost}/apps/{app}:startPush</summary>

**Header**

```http
Authorization: Basic {credentials}

# Authorization
    Credentials for HTTP Basic Authentication created with <AccessToken>
```

**Body : SRT**

```json
{
  "id": "{unique_push_id}",
  "stream": {
    "name": "{output_stream_name}",
    "variantNames": []
  },
  "protocol": "srt",
  "url": "srt://{host}[:port]?mode=caller&latency=120000&timeout=500000",
  "streamKey": ""
}

# id (required)
    unique ID to identify the task
    
# stream (required)
    ## name (required)
        output stream name
        
    ## variantNames (optional)
        Array of track names to publsh. 
        This value is Encodes.[Video|Audio|Data].Name in the OutputProfile
        setting.
        
        If empty, all tracks will be sent.

# protocol (required)
    srt
    
# url (required) 
    address of destination.
    options can be set in query-string format.
    
# streamKey (optional)
    not used with mpegts
```

In SRT Push Publisher, only the `caller` connection mode is supported.

**Body : RTMP**

{% code overflow="wrap" %}

```json
{
  "id": "{unique_push_id}",
  "stream": {
    "name": "{output_stream_name}",
    "variantNames": [ "h264_fhd", "aac" ]
  },
  "protocol": "rtmp",
  "url":"rtmp://{host}[:port]/{app_name}",
  "streamKey":"{stream_name}"
}

# id (required)
    unique ID to identify the task
    
# stream (required)
    ## name (required)
        output stream name
        
    ## variantNames (optional)
        Array of track names to publsh. 
        This value is Encodes.[Video|Audio|Data].Name in the OutputProfile
        setting.
        
        If empty, The first track among video tracks (by ID) and the first 
        track among audio tracks (by ID) are selected automatically.

# protocol (required)
    rtmp
    
# url (required) 
    address of destination
    
# streamKey (required)
    RTMP stream key
```

{% endcode %}

**Body : MPEG2-TS**

```json
{
  "id": "{unique_push_id}",
  "stream": {
    "name": "{output_stream_name}",
    "variantNames": []
  },
  "protocol": "mpegts",
  "url": "udp://{host}[:port]",
  "streamKey": ""
}

# id (required)
    unique ID to identify the task
    
# stream (required)
    ## name (required)
        output stream name
        
    ## variantNames (optional)
        Array of track names to publsh. 
        This value is Encodes.[Video|Audio|Data].Name in the OutputProfile
        setting.
        
        If empty, all tracks will be sent.

# protocol (required)
    mpegts
    
# url (required) 
    address of destination
    
# streamKey (optional)
    not used with mpegts
```

</details>

> **Responses**

<details>

<summary><mark style="color:blue;">200</mark> Ok</summary>

The request has succeeded

**Header**

```
Content-Type: application/json
```

**Body**

Please note that `responses` are incorrectly returned in Json array format for version 0.15.3 and earlier.

```json
{
    "statusCode": 200,
    "message": "OK",
    "response": {
        "id": "{unique_push_id}",
        "state": "ready",
            
        "vhost": "default",
        "app": "app",
        "stream": {
            "name": "{output_stream_name}",
            "trackIds": [],
            "variantNames": []
        },
            
        "protocol": "rtmp",
        "url": "rtmp://{host}[:port]/{app_name}",
        "streamKey": "{stream_name}",
            
        "sentBytes": 0,
        "sentTime": 0,
        "sequence": 0,
        "totalsentBytes": 0,
        "totalsentTime": 0,
            
        "createdTime": "2023-03-15T23:02:34.371+09:00",
        "startTime": "1970-01-01T09:00:00.000+09:00",
        "finishTime": "1970-01-01T09:00:00.000+09:00"
    }
}

# statusCode
    Same as HTTP Status Code
# message
    A human-readable description of the response code
# response
    Created push publishing task information
```

</details>

<details>

<summary><mark style="color:red;">400</mark> Bad Request</summary>

Invalid request.

</details>

<details>

<summary><mark style="color:red;">401</mark> Unauthorized</summary>

Authentication required

**Header**

```http
WWW-Authenticate: Basic realm=”OvenMediaEngine”
```

**Body**

```json
{
    "message": "[HTTP] Authorization header is required to call API (401)",
    "statusCode": 401
}
```

</details>

<details>

<summary><mark style="color:red;">404</mark> Not Found</summary>

The given vhost or application name could not be found.

**Body**

```json
{
    "message": "[HTTP] Could not find the application: [vhost/app1] (404)",
    "statusCode": 404
}
```

</details>

<details>

<summary><mark style="color:red;">409</mark> Conflict</summary>

duplicate ID

</details>

<table><thead><tr><th width="189.5555419921875">Parameter</th><th width="138.4444580078125" align="center">Requirement</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td align="center">Required</td><td><p>Push Publishing Task를 식별하기 위한 고유 ID 문자열입니다.</p><ul><li>ID 중복은 허용되지 않습니다.</li></ul></td></tr><tr><td><code>stream</code></td><td align="center">Required</td><td>Output Target Stream Information Object입니다.</td></tr><tr><td><code>stream.name</code></td><td align="center">Required</td><td><p>Output Stream의 Name입니다.</p><ul><li>예시: <code>"myStreamOut"</code>.</li></ul></td></tr><tr><td><code>stream.variantNames</code></td><td align="center">Optional</td><td><p>전송할 Track (Variant) Name 목록입니다.</p><ul><li><code>variantNames</code>를 생략하거나 지정하지 않으면, 모든 Track이 전송되거나, <code>RTMP</code>의 경우 첫번째 Video Track과 Audio Track이 선택됩니다.</li></ul></td></tr><tr><td><code>protocol</code></td><td align="center">Required</td><td><p>Push Publishing에 사용할 Protocol입니다.</p><ul><li>예시: <code>"srt"</code>, <code>"mpegts"</code>, <code>"rtmp"</code>.</li></ul></td></tr><tr><td><code>url</code></td><td align="center">Required</td><td><p>Destination URL입니다.</p><ul><li>Protocol별로 형식이 다르며 Query String 옵션을 사용할 수 있습니다.</li></ul></td></tr><tr><td><code>streamKey</code></td><td align="center">Optional</td><td><p><code>RTMP</code> 전송 시 필요한Stream Key입니다.</p><ul><li><code>MPEG-TS</code> 전송 시, 생략할 수 있습니다.</li></ul></td></tr><tr><td><code>timestampMode</code></td><td align="center"><p>Optional</p><p><sub><em><mark style="color:yellow;">(Enterprise Only)</mark></em></sub></p></td><td><p>Outgoing Packet의 Timestamp Mode를 설정합니다.</p><ul><li><code>ZeroBased</code>: 전송 시작 시점부터 Timestamp를 0으로 설정하여 전달합니다.</li><li> <code>Original</code> (default): 원본 Stream과 동일한 Timestamp를 전달합니다.</li></ul></td></tr></tbody></table>

### Push Publishing 중지하기

> **Request**

<details>

<summary><mark style="color:blue;">POST</mark> /v1/vhosts/{vhost}/apps/{app}:stopPush</summary>

**Header**

```http
Authorization: Basic {credentials}

# Authorization
    Credentials for HTTP Basic Authentication created with <AccessToken>
```

**Body**

{% code overflow="wrap" %}

```json
{
    "id": "{unique_push_id}"
}

# id (required)
    unique ID to identify the push publishing task
```

{% endcode %}

</details>

> **Responses**

<details>

<summary><mark style="color:blue;">200</mark> Ok</summary>

The request has succeeded

**Header**

```
Content-Type: application/json
```

**Body**

```json
{
    "statusCode": 200,
    "message": "OK",
}

# statusCode
	Same as HTTP Status Code
# message
	A human-readable description of the response code
```

</details>

<details>

<summary><mark style="color:red;">400</mark> Bad Request</summary>

Invalid request.

</details>

<details>

<summary><mark style="color:red;">401</mark> Unauthorized</summary>

Authentication required

**Header**

```http
WWW-Authenticate: Basic realm=”OvenMediaEngine”
```

**Body**

```json
{
    "message": "[HTTP] Authorization header is required to call API (401)",
    "statusCode": 401
}
```

</details>

<details>

<summary><mark style="color:red;">404</mark> Not Found</summary>

The given vhost/application name or id of recording task could not be found.

**Body**

```json
{
    "message": "[HTTP] Could not find the application: [vhost/app1] (404)",
    "statusCode": 404
}
```

</details>

### Push Publishing 상태 조회하기

> **Request**

<details>

<summary><mark style="color:blue;">POST</mark> /v1/vhosts/{vhost}/apps/{app}:pushes</summary>

**Header**

```http
Authorization: Basic {credentials}

# Authorization
    Credentials for HTTP Basic Authentication created with <AccessToken>
```

**Body**

{% code overflow="wrap" %}

```json
{
    "id": "{unique_push_id}"
}

# id (optional)
    unique ID to identify the push publishing task. If no id is given in the request, the full list is returned.
```

{% endcode %}

</details>

> **Responses**

<details>

<summary><mark style="color:blue;">200</mark> Ok</summary>

The request has succeeded

**Header**

```
Content-Type: application/json
```

**Body**

The `response` is <mark style="color:green;">Json array</mark> format.

```json
{
    "statusCode": 200,
    "message": "OK",
    "response": [
        {
            "id": "{unique_push_id}",
            "state": "started",
            
            "vhost": "default",
            "app": "app",
            "stream": {
                "name": "{output_stream_name}",
                "trackIds": [],
                "variantNames": []
            },
            
            "protocol": "rtmp",
            "url": "rtmp://{host}[:port]/{app_name}",
            "streamKey": "{stream_name}",
            
            "sentBytes": 0,
            "sentTime": 0,
            "sequence": 0,
            "totalsentBytes": 0,
            "totalsentTime": 0,
            
            "createdTime": "2023-03-15T23:02:34.371+09:00",
            "startTime": "1970-01-01T09:00:00.000+09:00",
            "finishTime": "1970-01-01T09:00:00.000+09:00"
        },
        {
            "id": "4",
            ...
        }
    ]
}

# statusCode
	Same as HTTP Status Code
# message
	A human-readable description of the response code
# response
	Information of recording tasks. If there is no recording task, 
	response with empty array ("response": [])
```

</details>

<details>

<summary><mark style="color:red;">401</mark> Unauthorized</summary>

Authentication required

**Header**

```http
WWW-Authenticate: Basic realm=”OvenMediaEngine”
```

**Body**

```json
{
    "message": "[HTTP] Authorization header is required to call API (401)",
    "statusCode": 401
}
```

</details>

<details>

<summary><mark style="color:red;">404</mark> Not Found</summary>

The given vhost or application name could not be found.

**Body**

```json
{
    "message": "[HTTP] Could not find the application: [vhost/app1] (404)",
    "statusCode": 404
}
```

</details>

#### Push Publishing 상태 목록

<table><thead><tr><th width="249.5555419921875">State</th><th>Description</th></tr></thead><tbody><tr><td>ready</td><td>Waiting for the stream to be created.</td></tr><tr><td>connecting</td><td>Connecting to destination</td></tr><tr><td>pushing</td><td>Connected and streaming</td></tr><tr><td>stopping</td><td>Disconnection / stop in progress</td></tr><tr><td>stopped</td><td>Push is disconnected / stopped</td></tr><tr><td>error</td><td>Push encountered an error</td></tr></tbody></table>
