플레이 콜백
Kollus VOD는 콘텐츠의 업로드부터 재생 까지 전체 워크플로우에 대해 콜백(Callback) 연동을 지원합니다.
고객사는 콜백 기능을 사용하여 주요 이벤트 발생 시 지정된 URL로 실시간 HTTP 알림을 수신할 수 있으며, 이를 통해 외부 시스템과의 데이터를 동기화하거나 업무 프로세스를 자동화할 수 있습니다.
플레이 콜백(Play Callback)은 최종 사용자(End User)가 Kollus 플레이어에서 재생을 요청할 때 실시간으로 호출됩니다. 콜백 서버로부터 정상 응답을 수신한 경우에만 콘텐츠 재생이 시작됩니다.
유의 사항
- 채널 설정에 따른 동작 방식
- 콜백이 설정되지 않은 채널: 서버 승인 절차 없이 콘텐츠가 즉시 재생됩니다.
- 콜백이 설정된 채널: Kollus 시스템이 고객사 콜백 서버에 승인 여부를 묻고, 승인 응답을 받은 경우에만 콘텐츠가 재생됩니다.
- 콜백이 설정되지 않은 채널: 서버 승인 절차 없이 콘텐츠가 즉시 재생됩니다.
- 필수 구성 요소: 플레이 콜백을 사용하려면, 플레이어 호출 시 JWT 페이로드 내
client_user_id값을 포함해야 합니다. - 오프라인 재생 제어: 기기에 다운로드된 콘텐츠의 재생 제어는 플레이 콜백이 아닌 DRM 다운로드 콜백을 통해 처리됩니다.
자세한 내용은 DRM 다운로드 콜백 문서를 참고하세요.
콜백 설정 방법
콜백 URL은 Kollus VOD 콘솔에서 설정할 수 있습니다.
상세한 설정 위치는 콜백 설정 - 플레이 콜백 문서를 참고하세요.
콜백 흐름
⚠️주의
- 응답 형식: 콜백 서버의 응답은 반드시 JWT(JSON Web Token) 형식으로 인코딩되어야 합니다.
- 헤더 설정: JWT 헤더 내에 사용자 키(
X-KOLLUS-USERKEY)를 반드시 포함해야 합니다.
사용자 키는 Kollus VOD 콘솔 > [서비스 계정] > [사용자 키]에서 확인할 수 있습니다. - 데이터 타입 준수: 응답 JSON 페이로드 내의 모든 정수형 필드(
expiration_date,result,content_expired등)은 integer 타입으로 전달해야 합니다.
("1"처럼 string 타입으로 전송 시 처리 실패)
콜백 유형
플레이 콜백은 목적에 따라 다음 두 가지 유형으로 구분됩니다. 각 유형에 따라 응답해야 하는 파라미터 규격이 다릅니다.
kind1(재생 만료 설정): 재생 시작 전, 해당 사용자의 **재생 가능 기한(Expiration)**을 동적으로 설정하기 위해 호출됩니다.kind3(최종 재생 승인): 플레이어가 재생 준비를 마친 직후, 해당 사용자가 현재 시점에 재생 권한이 있는지 최종 승인 여부를 확인하기 위해 호출됩니다.
요청
전송 방식
- HTTP Method:
POST - Content-Type:
application/x-www-form-urlencoded - Data Format: 모든 데이터는 Form Data 형식으로 전달됩니다.
요청 파라미터
kind1과 kind3 요청은 공통된 파라미터 구조를 가집니다.
| 파라미터 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
kind | integer | ◯ | 플레이 콜백 유형
|
client_user_id | string | ◯ | 시청자 ID(JWT 생성 시 입력한 client_user_id와 동일) |
player_id | string | ◯ | 기기 고유 식별자 |
hardware_id | string | - | 기기 시리얼 넘버(PC에서만 제공) |
device_name | string | - | 기기 모델명 |
media_content_key | string | ◯ | 채널 내 콘텐츠의 고유 식별자 |
uservalues | JSON string | - | 고객사 정의 값(uservalue0~uservalue99) |
localtime | integer | - | 기기 UTC 시간 |
uservalues 예시
{
"uservalue0": "강의코드01",
"uservalue1": "상품코드02",
"uservalue9": "생성코드03"
}
응답
kind1 응답 필드
data 항목
⚠️주의
한 번 설정된 만료 옵션(expiration_date)은 Kollus 시스템 내에서 수정하거나 회수할 수 없습니다. 반드시 정확한 Unix Timestamp 값을 전송하세요.
| 필드 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
expiration_date | integer | ◯ | 재생 만료 시각(Unix Timestamp)
|
result | integer | ◯ |
|
message | string | - | result 값이 0일 때 플레이어에 표시할 메시지PC 플레이어는 재생 URL에 loadcheck=0 파라미터를 추가해야 메시지가 노출됩니다.예: http://v.kr.kollus.com/{미디어 콘텐츠 키}?loadcheck=0 |
vmcheck | integer | - | 가상머신 체크 여부(Html5 Player for PC에서만 사용 가능)
|
disable_tvout | integer | - | TV 출 력 제한 설정(채널 기본 정책보다 우선 적용)
|
expiration_playtime | integer | - | 재생 제한 시간(단위: 초)
|
cpcheck | integer | - | 캡처 프로그램 감지 기능 여부(PC)
|
exp 항목
| 필드 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
exp | integer | - | JWT 만료 시각(Unix Timestamp) |
kind1 응답 예시
{
"data" : {
"expiration_date": 1402444800,
"vmcheck": 1,
"disable_tvout": 1,
"expiration_playtime": 1800,
"result": 1
},
"exp" : 1477558242
}
kind3 응답 필드
data 항목
| 필드 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
content_expired | integer | - | 재생 만료 여부
|
result | integer | ◯ | 승인 결과
result 값이 content_expired보다 우선하여 적용됩니다. |
message | string | - | result가 0이거나 content_expired가 1일 때 표시할 메시지PC 플레이어는 재생 URL에 loadcheck=0 파라미터를 추가해야 메시지가 노출됩니다.예: http://v.kr.kollus.com/{미디어 콘텐츠 키}?loadcheck=0 |
exp 항목
| 필드 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
exp | integer | - | JWT 만료 시각(Unix Timestamp) |
kind3 응답 예시
{
"data" : {
"content_expired": 1,
"result": 1
},
"exp" : 1477558242
}
Java/Spring 사용 예제
JWT 인코딩 예제
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;
import com.fasterxml.jackson.databind.ObjectMapper;
public class KollusJwtEncoder {
private final ObjectMapper objectMapper = new ObjectMapper();
public String encode(Map<String, Object> payload, String key) throws Exception {
Map<String, String> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
String headerJson = objectMapper.writeValueAsString(header);
String payloadJson = objectMapper.writeValueAsString(payload);
String encodedHeader = Base64.getUrlEncoder().withoutPadding()
.encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
String encodedPayload = Base64.getUrlEncoder().withoutPadding()
.encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
String unsignedToken = encodedHeader + "." + encodedPayload;
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmac.init(secretKey);
byte[] signatureBytes = hmac.doFinal(unsignedToken.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getUrlEncoder().withoutPadding()
.encodeToString(signatureBytes);
return unsignedToken + "." + signature;
}
}
Spring Boot 플레이 콜백 예제
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
import java.util.*;
@SpringBootApplication
@RestController
public class KollusPlayCallbackApplication {
private static final String KOLLUS_KEY = "**사용자키**";
private static final String JWT_KEY = "**보안키**";
private final KollusJwtEncoder jwtEncoder = new KollusJwtEncoder();
/**
* 플레이 콜백
* 엔드포인트: /play/callback
* HTTP Method: POST
* Content-Type: application/x-www-form-urlencoded
*/
@PostMapping("/play/callback")
public ResponseEntity<String> playCallback(
@RequestParam("kind") Integer kind,
@RequestParam("client_user_id") String clientUserId,
@RequestParam("player_id") String playerId,
@RequestParam(value = "device_name", required = false) String deviceName,
@RequestParam("media_content_key") String mediaContentKey,
@RequestParam(value = "hardware_id", required = false) String hardwareId,
@RequestParam(value = "uservalues", required = false) String uservalues,
@RequestParam(value = "localtime", required = false) Long localtime) {
try {
System.out.println("Play Callback - kind: " + kind +
", user: " + clientUserId +
", media: " + mediaContentKey);
Map<String, Object> responseData;
if (kind == 1) {
responseData = processKind1(clientUserId, mediaContentKey);
} else if (kind == 3) {
responseData = processKind3(clientUserId, playerId, mediaContentKey);
} else {
responseData = createErrorResponse("Invalid kind");
}
// JWT 생성
String jwtToken = jwtEncoder.encode(responseData, JWT_KEY);
// 응답 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.set("X-Kollus-UserKey", KOLLUS_KEY);
headers.setContentType(MediaType.TEXT_PLAIN);
return ResponseEntity.ok().headers(headers).body(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.internalServerError().body("Error processing callback");
}
}
/**
* kind1 처리 (재생 만료 설정)
*/
private Map<String, Object> processKind1(String clientUserId, String mediaContentKey) {
// TODO: DB에서 사용자 및 콘텐츠 권한 확인
Map<String, Object> data = new HashMap<>();
data.put("result", 1);
data.put("expiration_date", Instant.now().getEpochSecond() + 86400); // 1일
data.put("vmcheck", 1);
data.put("disable_tvout", 0);
data.put("expiration_playtime", 1800); // 30분
data.put("cpcheck", 1);
Map<String, Object> response = new HashMap<>();
response.put("data", data);
response.put("exp", Instant.now().getEpochSecond() + 3600); // JWT 1시간 유효
return response;
}
/**
* kind3 처리 (최종 재생 승인)
*/
private Map<String, Object> processKind3(String clientUserId, String playerId,
String mediaContentKey) {
// TODO: DB에서 재생 권한 및 만료 여부 확인
Map<String, Object> data = new HashMap<>();
data.put("result", 1);
data.put("content_expired", 0); // 0: 재생 가능, 1: 재생 차단
Map<String, Object> response = new HashMap<>();
response.put("data", data);
response.put("exp", Instant.now().getEpochSecond() + 3600);
return response;
}
/**
* 에러 응답 생성
*/
private Map<String, Object> createErrorResponse(String message) {
Map<String, Object> data = new HashMap<>();
data.put("result", 0);
data.put("message", message);
Map<String, Object> response = new HashMap<>();
response.put("data", data);
return response;
}
public static void main(String[] args) {
pringApplication.run(KollusPlayCallbackApplication.class, args);
}
}