# Ad Markers

## CUE-OUT/IN

#### For LL-HLS/HLS

REST API를 사용하여 LL-HLS 및 HLS playlist 내에 동적으로 Ad Marker를 삽입 할 수 있습니다. `CUE-OUT` 이벤트를 호출하게 되면 `playlist` 내에 아래와 같이 태그가 추가됩니다.

```
#EXT-X-CUE-OUT:DURATION=<time>
…
#EXT-X-CUE-IN
```

<table><thead><tr><th width="175">Element</th><th>Description</th></tr></thead><tbody><tr><td><code>#EXT-X-CUE-OUT</code>,<br><code>#EXT-X-CUE-IN</code></td><td><code>#EXT-X-CUE-OUT</code>과 <code>#EXT-X-CUE-IN</code>은 한쌍이며, 두 Tag 사이의 전체 Section은 광고 서버에 의해 광고 컨텐츠로 대체됩니다.</td></tr><tr><td><code>DURATION=&#x3C;time></code></td><td><code>DURATION=&#x3C;time></code> 은 필수이며, 광고의 유지 시간을 나타냅니다.</td></tr></tbody></table>

`#EXT-X-CUE-IN` Event를 호출하여 삽입된 광고를 조기에 종료 할 수 있습니다. Event 호출 시, `playlist`에 `#EXT-X-CUE-IN` Tag가 즉시 추가되며 기존에 추가되어 있던 `#EXT-X-CUE-IN` Tag는 삭제됩니다.

### API Interface

**Request**

<details>

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

**Header**

```http
Authorization: Basic {credentials}

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

**Body**

```json
{
  "eventFormat": "cue",
  "events":[
    {
      "cueType": "out", // out | in
      "duration": 60500 // milliseconds, only available when cueType is out
    }
  ]
}
```

</details>

<details>

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

**Header**

```http
Authorization: Basic {credentials}

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

**Body**

```json
[
  {
    "eventFormat": "id3v2",
    "eventType": "video", // "eventTarget": "video" is same
    "events":[
      {
        "frameType": "TXXX",
        "info": "AirenSoft",
        "data": "OvenMediaEngine"
      },
      {
        "frameType": "TIT2",
        "data": "OvenMediaEngine 123"
      }
    ]
  },
  {
    "eventFormat": "cue",
    "events":[
      {
        "cueType": "out", // out | in
        "duration": 60500 // milliseconds, only available when cueType is out
      }
    ]
  }
]
```

</details>

**Responses**

<details>

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

The request has succeeded

**Header**

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

**Body**

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

</details>

<details>

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

Invalid request. Body is not a JSON object or does not have a required value

</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 name or application name could not be found.

**Body**

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

</details>

## SCTE-35 Event 삽입

#### For LL-HLS/HLS

REST API를 사용하여 LL-HLS 및 HLS playlist 내에 ASd Marker를 `#EXT-X-DATERANGE`로 삽입할 수 있습니다. `#EXT-X-DATERANGE`는 SCTE-35 OUT/IN 특성을 통해 광고 시간의 타이밍을 지정합니다.

#### For SRT Push

OvenMediaEngine Enterprise 0.20.0.0-1 이상 버전부터 LL-HLS 및 HLS playlist 뿐만 아니라, SRT Push에도 SCTE-35 규격의 Event (`splice_insert()`)를 삽입할 수 있습니다.  `sendEvents` API를 통해 광고 시작/종료 신호 (OUT/IN) 또는 기타 Custom Event를 OvenMediaEngine Enterprise에 전달하면, 해당 정보가 SRT Push에 삽입되어 다른 System으로 전달됩니다.

### 동작 규칙

1. `OUT` Event 수신 시, 지정한 `duration` (ms) 경과 후 `IN`이 자동으로 삽입됩니다.
2. 지정한 `duration` 만료 이전에 `IN`을 삽입하면, 이전에 자동으로 삽입한 `IN`은 삭제됩니다.

{% hint style="warning" %}
일부 Downstream 장비가 광고 복귀를 감지하지 못하는 경우가 있으므로, `OUT` Event 전송 후 `IN` Event 전송을 권장합니다.
{% endhint %}

### API Interface

**Request**

<details>

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

**Header**

```http
Authorization: Basic {credentials}

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

**Body**

```json
{
  "eventFormat": "scte35",
  "events":[
    {
      "id": {{randomId}}, // required, 32bits unsigned number, auto filled if not present
      "type": "out", // required, out | in
      "duration": 10000 // milliseconds, only available when cueType is out
    }
  ]
}
```

</details>

<details>

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

**Header**

```http
Authorization: Basic {credentials}

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

**Body**

```json
[
  {
    "eventFormat": "scte35",
    "events":[
      {
        "spliceCommand": "spliceInsert",
        "id": {{randomId}}, // required, 32bits unsigned number, auto filled if not present
        "type": "out", // required, out | in
        "duration": 30000 // milliseconds, only available when cueType is out
      }
    ]
  }
]
```

</details>

{% hint style="info" %}
`duration`을 밀리초 (ms) 단위로 입력하지만, LL-HLS 및 HLS playlist에는 초 (s) 단위의 `PLANNED-DURATION` 으로 출력됩니다.
{% endhint %}

**Responses**

<details>

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

The request has succeeded

**Header**

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

**Body**

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

</details>

<details>

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

Invalid request. Body is not a JSON object or does not have a required value

</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 name or application name could not be found.

**Body**

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

</details>

### Example: Event 삽입 성공

#### For LL-HLS/HLS

SCTE-35 Event가 삽입 된 LLHLS playlist 예제입니다:

```
#EXT-X-DATERANGE:ID="123",START-DATE="2025-01-01T09:15:00+00:00",PLANNED-DURATION=10.0,SCTE35-OUT=0xF...
...
#EXT-X-DATERANGE:ID="123",START-DATE="2025-01-01T09:15:00+00:00",SCTE35-IN=0xF
```

<table><thead><tr><th width="175">Element</th><th>Description</th></tr></thead><tbody><tr><td><code>SCTE35-OUT</code></td><td>광고 시작 (본편 → 광고)을 나타내는 SCTE-35 Payload입니다.</td></tr><tr><td><code>SCTE35-IN</code></td><td>광고 종료 (광고 → 본편)를 나타내는 SCTE-35 Payload입니다.</td></tr><tr><td><code>PLANNED-DURATION</code></td><td>광고 유지 시간 (초)입니다. <code>OUT</code>과 함께 사용 시, 해당 시간 경과 후 자동으로 <code>IN</code>이 삽입됩니다.</td></tr><tr><td><code>ID</code></td><td>OUT/IN 동일 대상임을 구분하는 식별자입니다.<br><mark style="color:orange;">*  32-bit 부호 없는 숫자로 구성됩니다.</mark></td></tr><tr><td><code>START-DATE</code></td><td>구간 시작 시각 (ISO-8601)입니다.<br><mark style="color:orange;">* yyyy-mm-ddThh:mm:ss±UTC</mark></td></tr></tbody></table>

#### For SRT Push

아래와 같이 OvenMediaEngine Enterprise Log에 출력되었다면 정상적으로 SCTE-35 Event가 전달된 것입니다:

```
[11-03 21:29:02.028] D [SW-Push:2415407] FFmpegWriter | writer.cpp:523  | SCTE-35 Event: SpliceCommandType=5, ID=2025, OutOfNetwork=true, Timestamp=372370 ms, Duration=30000 ms, AutoReturn=false
```
