Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

- Uplynk
- Added `orderedPreplayParameters` to `UplynkSSAIConfiguration`, which can be used to maintain the order of preplay parameters when making a request.

### Fixed

- Uplynk
- Improved URL encoding for characters such as `%`, `&`, `=`, `+` and `,` to preserve pre-encoded values and prevent server-side double decoding issues.

### Changed

- Uplynk
- When ping feature is not configured, the player will now send an empty string instead of `"&ad.pingc=0"` to prevent unsigned parameters that could break signature validation.

## [10.8.0.1] - 2026-01-20

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ extension UplynkSSAIConfiguration {
}

var urlParameters: String {
if let orderedParams = orderedPreplayParameters, !orderedParams.isEmpty {
// Define strict allowed characters for query values
// We MUST encode '%' (to preserve pre-encoded values), '&', '=', '+', and others that alter URL structure.
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: "&+=?%,")

let joinedParameters = orderedParams.map { (key, value) in
let encodedValue = value.addingPercentEncoding(withAllowedCharacters: allowed) ?? value
return "\(key)=\(encodedValue)"
}.joined(separator: "&")
return "&\(joinedParameters)"
}

guard !preplayParameters.isEmpty else {
return ""
}
Expand All @@ -31,9 +44,9 @@ extension UplynkSSAIConfiguration {
var pingParameters: String {
let pingFeature = pingFeature
if pingFeature == .noPing {
return "&ad.pingc=0"
return ""
} else {
return "&ad.pingc=1&ad.pingf=\(pingFeature.rawValue)"
return "&ad.cping=1&ad.pingf=\(pingFeature.rawValue)"
}
}

Expand Down
3 changes: 3 additions & 0 deletions Code/Uplynk/Source/UplynkSSAIConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class UplynkSSAIConfiguration: CustomServerSideAdInsertionConfiguration {
public let id: ID
public let prefix: String?
public let preplayParameters: [String: String]
public let orderedPreplayParameters: [(String, String)]?
public let assetType: AssetType
public let contentProtected: Bool
public let assetInfo: Bool
Expand All @@ -38,6 +39,7 @@ public class UplynkSSAIConfiguration: CustomServerSideAdInsertionConfiguration {
assetType: AssetType,
prefix: String? = nil,
preplayParameters: [String: String] = [:],
orderedPreplayParameters: [(String, String)]? = nil,
contentProtected: Bool = false,
assetInfo: Bool = false,
uplynkPingConfiguration: UplynkPingConfiguration = .init(),
Expand All @@ -47,6 +49,7 @@ public class UplynkSSAIConfiguration: CustomServerSideAdInsertionConfiguration {
self.assetType = assetType
self.prefix = prefix
self.preplayParameters = preplayParameters
self.orderedPreplayParameters = orderedPreplayParameters
self.contentProtected = contentProtected
self.assetInfo = assetInfo
self.pingConfiguration = uplynkPingConfiguration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ final class UplynkSSAIConfigurationURLBuilderTests: XCTestCase {
let prefix = "https://content.uplynk.com"
let assetID = "a123"

let validNoPingQueryParameter = "ad.pingc=0"
let validPingQueryParameter = "ad.pingc=1&ad.pingf=\(pingFeature.rawValue)"
let validNoPingQueryParameter = ""
let validPingQueryParameter = "ad.cping=1&ad.pingf=\(pingFeature.rawValue)"

let pingConfiguration = switch pingFeature {
case .noPing:
Expand Down
4 changes: 3 additions & 1 deletion Code/Uplynk/docs/preplay.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ We start by creating an `UplynkSSAIConfiguration` object that describes how to c

- `preplayParameters`: The `preplayParameters` object should have string-key-string-value combinations, which will be used as query parameters for the Preplay API call. Nested objects are not supported.

- `orderedPreplayParameters`: The `orderedPreplayParameters` object should have string-key-string-value combinations, which will be used as query parameters for the Preplay API call. Nested objects are not supported. Unlike `preplayParameters`, `orderedPreplayParameters` preserves the order of parameters while making a request, which is neccessary to prevent unsigned parameters that could break signature validation.

- `contentProtected`: Boolean value which will internally set any necessary content-protection information. No content-protection details have to be specified by the customer.

- **A Preplay request must include all parameters defined within the playback request, hence these parameters must be included in the THEOplayer source**. This request must also include a digital signature if the 'Require a token for playback' option is enabled in the back-end on the corresponding live channel. (See also : [Signing a Playback URL Tutorial](https://docs.uplynk.com/docs/sign-playback-url))
Expand Down Expand Up @@ -62,7 +64,7 @@ Ad specific parameters can be passed in the `preplayParameters` argument of the
assetType: ...,
prefix: ...,
preplayParameters: [
// Parameters here should specify the necessary ad parameters for the Preplay API
// Parameters here should specify the necessary ad parameters for the Preplay API. Use `orderedPreplayParameters` instead to pass these in the order given.
"ad.param1": "param_val1",
"ad.param2": "param_val2"
],
Expand Down