# 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
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ovenmediaengine-enterprise.gitbook.io/guide/ko-kr/features/workflow-integration-and-external-system-connectivity/ad-markers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
