Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/1.3.0 #31

Merged
merged 31 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9d13de1
CORE-4594 Playlist API integration
Sep 27, 2024
5327ff7
CORE-4594 Playlist API integration
Oct 1, 2024
90577b5
CORE-4594 Playlist API integration
Oct 3, 2024
ea0c7d9
CORE-4594 Playlist API integration
Oct 9, 2024
906752f
CORE-4594 Playlist API integration
Oct 23, 2024
0f6505a
CORE-4594 Fixing test
Oct 25, 2024
0365480
Update Sources/PlaybackSDK/PlayerUIView/PlaybackUIView.swift
StefanoStream Oct 28, 2024
6d2286b
Update Sources/PlaybackSDK/Player Plugin/BitMovinPlugin/BitmovinPlaye…
StefanoStream Oct 28, 2024
96f282b
Update Sources/PlaybackSDK/Player Plugin/BitMovinPlugin/BitmovinPlaye…
StefanoStream Oct 28, 2024
c177c24
Playlist unit test cases
Oct 28, 2024
349ce61
CORE-4594 Playlist API integration
Oct 28, 2024
0b6e45b
Merge branch 'feature/playlist_unit-test-cases' into feature/CORE-459…
Oct 28, 2024
be5c99b
CORE-5100 CORE-5085 Playlist documentation
Nov 1, 2024
c576a85
CORE-4594 Playlist API integration
StefanoStream Nov 13, 2024
a30b267
Merge branch 'feature/CORE-4594-iOS-Playlist-API-integration' into fe…
StefanoStream Nov 14, 2024
7280775
CORE-5100 Playlist documentation
StefanoStream Nov 21, 2024
be2019b
CORE-4594 Playlist API integration
StefanoStream Nov 21, 2024
858ba2e
CORE-5100 Playlist documentation
StefanoStream Nov 27, 2024
2ab5b3c
CORE-4594 Playlist API integration
StefanoStream Nov 28, 2024
f224ab9
CORE-5100 Playlist documentation
StefanoStream Nov 28, 2024
ad6d29e
CORE-4594 Playlist API integration
StefanoStream Nov 29, 2024
c7c2b4a
CORE-5100 Playlist documentation
StefanoStream Nov 29, 2024
8170e41
Merge pull request #29 from StreamAMG/feature/CORE-5100-Playlist-docu…
StefanoStream Nov 29, 2024
7a2b39f
CORE-4594 Playlist API integration
StefanoStream Nov 29, 2024
0029ac4
CORE-4594 Playlist API integration
StefanoStream Dec 2, 2024
4760107
CORE-4594 Playback API integration
StefanoStream Dec 2, 2024
7dcebf7
CORE-4594 Playback API integration
StefanoStream Dec 2, 2024
494db35
CORE-4594 Playlist API integration
StefanoStream Dec 2, 2024
2318ff7
CORE-4594 Playlist API integration
StefanoStream Dec 5, 2024
b351d10
CORE-4594 Playlist API integration
StefanoStream Dec 5, 2024
a014b9e
Merge pull request #26 from StreamAMG/feature/CORE-4594-iOS-Playlist-…
StefanoStream Dec 5, 2024
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
Prev Previous commit
Next Next commit
CORE-4594 Playlist API integration
- New public PlaybackVideoDetails class as @artem-y-pamediagroup suggested on PR #26
- Rollback PlaybackResponseModel to internal struct
- Renaming PlayBack API folder to Playback API
- Added userAgent param comment on PlaybackAPI and PlaybackAPIService
- Fixed indentation on parameters comments
  • Loading branch information
StefanoStream committed Dec 2, 2024
commit 0029ac40bd855795b29e2af659c60c0dbda778e1
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ internal protocol PlaybackAPI {
Retrieves video details for a given entry ID.

- Parameters:
- entryId: The unique identifier of the video entry.
- andAuthorizationToken: Optional authorization token, can be nil for free videos.
- entryId: The unique identifier of the video entry.
- andAuthorizationToken: Optional authorization token, can be nil for free videos.
- userAgent: Custom `User-Agent` header to use with playback requests. Can be used if there was a custom header set to start session request. Defaults to `nil`
- Returns: A publisher emitting a result with a response model with an error or a critical error.
*/
func getVideoDetails(forEntryId entryId: String, andAuthorizationToken: String?, userAgent: String?) -> AnyPublisher<Result<PlaybackResponseModel, Error>, Error>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal class PlaybackAPIService: PlaybackAPI {
- Parameters:
- entryId: The unique identifier of the video entry.
- andAuthorizationToken: Optional authorization token, can be nil for free videos.
- userAgent: Custom `User-Agent` header to use with playback requests. Can be used if there was a custom header set to start session request. Defaults to `nil`
- Returns: A publisher emitting a result with a response model with an error or a critical error.
*/
func getVideoDetails(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,15 @@ internal struct PlaybackResponseModel: Decodable {
}

}

extension PlaybackResponseModel {
func toVideoDetails() -> PlaybackVideoDetails? {
if let entryId = self.entryId {
let videoDetails = PlaybackVideoDetails(videoId: entryId, url: self.media?.hls, title: self.name, thumbnail: self.coverImg?._360?.absoluteString, description: self.description)
return videoDetails
}
return nil
}
}

#endif
28 changes: 28 additions & 0 deletions Sources/PlaybackSDK/Playback API/PlaybackVideoDetails.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// PlaybackVideoDetails.swift
// PlaybackSDK
//
// Created by Stefano Russello on 29/11/24.
//

#if !os(macOS)
import Foundation

public class PlaybackVideoDetails {

public var videoId: String
public var url: String?
public var title: String?
public var thumbnail: String?
public var description: String?

public init(videoId: String, url: String? = nil, title: String? = nil, thumbnail: String? = nil, description: String? = nil) {
self.videoId = videoId
self.url = url
self.title = title
self.thumbnail = thumbnail
self.description = description
}
}

#endif
61 changes: 34 additions & 27 deletions Sources/PlaybackSDK/PlaybackSDKManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,16 @@ public class PlaybackSDKManager {
/// Initializes the `PlaybackSDKManager`.
public init() {}

/// Initializes the SDK with the provided API key.
/// This fuction must be called in the AppDelegate
///
/// - Parameters:
/// - apiKey: The API key for initializing the SDK.
/// - baseURL: The base URL for API endpoints. Defaults to `nil`.
/// - userAgent: Custom `User-Agent` header to use with playback requests. Can be used if there was a custom header set to start session request. Defaults to `nil`
/// - completion: A closure to be called after initialization.
/// It receives a result indicating success or failure.
/**
Initializes the SDK with the provided API key.
This fuction must be called in the AppDelegate

- Parameters:
- apiKey: The API key for initializing the SDK.
- baseURL: The base URL for API endpoints. Defaults to `nil`.
- userAgent: Custom `User-Agent` header to use with playback requests. Can be used if there was a custom header set to start session request. Defaults to `nil`
- completion: A closure to be called after initialization. It receives a result indicating success or failure.
*/
public func initialize(apiKey: String, baseURL: String? = nil, userAgent: String? = nil, completion: @escaping (Result<String, Error>) -> Void) {
guard !apiKey.isEmpty else {
completion(.failure(SDKError.initializationError))
Expand All @@ -161,15 +162,16 @@ public class PlaybackSDKManager {
Loads a video player with the specified entry ID and authorization token.

- Parameters:
- entryID: The unique identifier of the video entry to be loaded.
- authorizationToken: The token used for authorization to access the video content.
- onError: Return potential playback errors that may occur during the loading process.
- entryID: The unique identifier of the video entry to be loaded.
- authorizationToken: The token used for authorization to access the video content.
- onError: Return potential playback errors that may occur during the loading process.

- Returns: A view representing the video player configured with the provided entry ID and authorization token.

Example usage:
```swift
let playerView = loadPlayer(entryID: "exampleEntryID", authorizationToken: "exampleToken")
```
*/
public func loadPlayer(
entryID: String,
Expand All @@ -189,16 +191,17 @@ public class PlaybackSDKManager {
Loads a video player with the specified entry ID and authorization token.

- Parameters:
- entryIDs: A list of the videos to be loaded.
- entryIDToPlay: The first video Id to be played. If not provided, the first video in the entryIDs array will be played.
- authorizationToken: The token used for authorization to access the video content.
- onErrors: Return a list of potential playback errors that may occur during the loading process for single entryId.
- entryIDs: A list of the videos to be loaded.
- entryIDToPlay: The first video Id to be played. If not provided, the first video in the entryIDs array will be played.
- authorizationToken: The token used for authorization to access the video content.
- onErrors: Return a list of potential playback errors that may occur during the loading process for single entryId.

- Returns: A view representing the video player configured with the provided entry ID and authorization token.

Example usage:
```swift
let playerView = loadPlayer(entryIDs: ["exampleEntryID1", "exampleEntryID2"], authorizationToken: "exampleToken")
```
*/
public func loadPlaylist(
entryIDs: [String],
Expand Down Expand Up @@ -367,40 +370,44 @@ public class PlaybackSDKManager {
}
}

func createSource(from details: PlaybackResponseModel, authorizationToken: String?) -> Source? {

guard let hlsURLString = details.media?.hls, let hlsURL = URL(string: hlsURLString) else {
func createSource(from details: PlaybackVideoDetails, authorizationToken: String?) -> Source? {
guard let hlsURLString = details.url, let hlsURL = URL(string: hlsURLString) else {
return nil
}

let sourceConfig = SourceConfig(url: hlsURL, type: .hls)

// Avoiding to fill all the details (title, thumbnail and description) for now
// Because when the initial video is not the first one and we have to seek the first source
// Bitmovin SDK has a bug/glitch that show the title/thumbnail of the first video for a short time before changing to the new one
// sourceConfig.title = details.name
// sourceConfig.posterSource = details.coverImg?._360
// sourceConfig.title = details.title
// if let thumb = details.thumbnail, let thumbUrl = URL(string: thumb) {
// sourceConfig.posterSource = thumbUrl
// }
// sourceConfig.sourceDescription = details.description

if details.entryId?.isEmpty == false {
sourceConfig.metadata["entryId"] = details.entryId
if details.videoId.isEmpty == false {
sourceConfig.metadata["entryId"] = details.videoId
} else {
// Recover entryId from hls url (not working for live url)
// If entryId is null, get the entryId from HLS url (not working for live url)
let regex = try! NSRegularExpression(pattern: "/entryId/(.+?)/")
let range = NSRange(location: 0, length: hlsURLString.count)
if let match = regex.firstMatch(in: hlsURLString, options: [], range: range) {
if let swiftRange = Range(match.range(at: 1), in: hlsURLString) {
let entryId = hlsURLString[swiftRange]
sourceConfig.metadata["entryId"] = String(entryId)

let entryIdFromUrl = hlsURLString[swiftRange]
sourceConfig.metadata["entryId"] = String(entryIdFromUrl)
}
}
}

// Adding extra details
sourceConfig.metadata["details"] = details
sourceConfig.metadata["authorizationToken"] = authorizationToken

return SourceFactory.createSource(from: sourceConfig)
}

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class BitmovinPlayerPlugin: VideoPlayerPlugin, ObservableObject {
playerConfig.styleConfig.userInterfaceConfig = uiConfig
}

public func playerView(videoDetails: [PlaybackResponseModel], entryIDToPlay: String?, authorizationToken: String?) -> AnyView {
public func playerView(videoDetails: [PlaybackVideoDetails], entryIDToPlay: String?, authorizationToken: String?) -> AnyView {
self.authorizationToken = authorizationToken
self.entryIDToPlay = entryIDToPlay
// Create player based on player and analytics configurations
Expand Down Expand Up @@ -211,9 +211,13 @@ public class BitmovinPlayerPlugin: VideoPlayerPlugin, ObservableObject {
if let entryId = entryId {
PlaybackSDKManager.shared.loadHLSStream(forEntryId: entryId, andAuthorizationToken: authorizationToken) { result in
switch result {
case .success(let videoDetails):
let newSource = PlaybackSDKManager.shared.createSource(from: videoDetails, authorizationToken: authorizationToken)
completion(newSource)
case .success(let response):
if let videoDetails = response.toVideoDetails() {
let newSource = PlaybackSDKManager.shared.createSource(from: videoDetails, authorizationToken: authorizationToken)
completion(newSource)
} else {
completion(nil)
}
case .failure:
break
completion(nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,19 @@ public struct BitmovinPlayerView: View {
return sConfig
}

/// Initializes the view with the player passed from outside.
///
/// This version of the initializer does not modify the `player`'s configuration, so any additional configuration steps
/// like setting `userInterfaceConfig` should be performed externally.
///
/// - parameter videoDetails: Full videos details containing name, description, thumbnail, duration as well as URL of the HLS video stream that will be loaded by the player as the video source
/// - parameter player: Instance of the player that was created and configured outside of this view.
public init(videoDetails: [PlaybackResponseModel], entryIDToPlay: String?, authorizationToken: String?, player: Player) {
/**
Initializes the view with the player passed from outside.

This version of the initializer does not modify the `player`'s configuration, so any additional configuration steps
like setting `userInterfaceConfig` should be performed externally.

- Parameters:
- videoDetails: Full videos details containing title, description, thumbnail, duration as well as URL of the HLS video stream that will be loaded by the player as the video source
- entryIDToPlay: (Optional) The first video Id to be played. If not provided, the first video in the entryIDs array will be played.
- authorizationToken: (Optional) The token used for authorization to access the video content.
- player: Instance of the player that was created and configured outside of this view.
*/
public init(videoDetails: [PlaybackVideoDetails], entryIDToPlay: String?, authorizationToken: String?, player: Player) {

self.player = player
self.authorizationToken = authorizationToken
Expand All @@ -55,19 +60,24 @@ public struct BitmovinPlayerView: View {

sources = createPlaylist(from: videoDetails)

setupRemoteCommandCenter(title: videoDetails.first?.name ?? "")
setupRemoteCommandCenter(title: videoDetails.first?.title ?? "")
}

/// Initializes the view with an instance of player created inside of it, upon initialization.
///
/// In this version of the initializer, a `userInterfaceConfig` is being added to the `playerConfig`'s style configuration.
///
/// - Note: If the player config had `userInterfaceConfig` already modified before passing into this `init`,
/// those changes will take no effect sicne they will get overwritten here.
///
/// - parameter hlsURLString: Full videos details containing name, description, thumbnail, duration as well as URL of the HLS video stream that will be loaded by the player as the video source
/// - parameter playerConfig: Configuration that will be passed into the player upon creation, with an additional update in this initializer.
public init(videoDetails: [PlaybackResponseModel], entryIDToPlay: String?, authorizationToken: String?, playerConfig: PlayerConfig) {
/**
Initializes the view with an instance of player created inside of it, upon initialization.

In this version of the initializer, a `userInterfaceConfig` is being added to the `playerConfig`'s style configuration.

- Note: If the player config had `userInterfaceConfig` already modified before passing into this `init`,
those changes will take no effect since they will get overwritten here.

- Parameters:
- videoDetails: Full videos details containing title, description, thumbnail, duration as well as URL of the HLS video stream that will be loaded by the player as the video source
- entryIDToPlay: (Optional) The first video Id to be played. If not provided, the first video in the entryIDs array will be played.
- authorizationToken: (Optional) The token used for authorization to access the video content.
- playerConfig: Configuration that will be passed into the player upon creation, with an additional update in this initializer.
*/
public init(videoDetails: [PlaybackVideoDetails], entryIDToPlay: String?, authorizationToken: String?, playerConfig: PlayerConfig) {

let uiConfig = BitmovinUserInterfaceConfig()
uiConfig.hideFirstFrame = true
Expand All @@ -82,7 +92,7 @@ public struct BitmovinPlayerView: View {

sources = createPlaylist(from: videoDetails)

setupRemoteCommandCenter(title: videoDetails.first?.name ?? "")
setupRemoteCommandCenter(title: videoDetails.first?.title ?? "")
}

public var body: some View {
Expand Down Expand Up @@ -116,7 +126,7 @@ public struct BitmovinPlayerView: View {
}
}

func createPlaylist(from videoDetails: [PlaybackResponseModel]) -> [Source] {
func createPlaylist(from videoDetails: [PlaybackVideoDetails]) -> [Source] {
var sources: [Source] = []
for details in videoDetails {

Expand Down
5 changes: 1 addition & 4 deletions Sources/PlaybackSDK/Player Plugin/VideoPlayerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ public protocol VideoPlayerPlugin: AnyObject {

func setup(config: VideoPlayerConfig)

// TODO: add event
/// func handleEvent(event: BitmovinPlayerCore.PlayerEvent)

func playerView(videoDetails: [PlaybackResponseModel], entryIDToPlay: String?, authorizationToken: String?) -> AnyView
func playerView(videoDetails: [PlaybackVideoDetails], entryIDToPlay: String?, authorizationToken: String?) -> AnyView

func play()

Expand Down
21 changes: 14 additions & 7 deletions Sources/PlaybackSDK/PlayerUIView/PlaybackUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal struct PlaybackUIView: View {
@State private var hasFetchedVideoDetails = false

/// The fetched video details of the entryIDs
@State private var videoDetails: [PlaybackResponseModel]?
@State private var videoDetails: [PlaybackVideoDetails]?
/// Array of errors for fetching playlist details
@State private var playlistErrors: [PlaybackAPIError]?
/// Error of failed API call for loading video details
Expand All @@ -44,8 +44,10 @@ internal struct PlaybackUIView: View {
Initializes the `PlaybackUIView` with the provided list of entry ID and authorization token.

- Parameters:
- entryId: A list of entry ID of the video to be played.
- authorizationToken: Optional authorization token if required to fetch the video details.
- entryIds: A list of entry ID of the video to be played.
- entryIDToPlay: (Optional) The first video Id to be played. If not provided, the first video in the entryIDs array will be played.
- authorizationToken: (Optional) Authorization token if required to fetch the video details.
- onErrors: Return a list of potential playback errors that may occur during the loading process for single entryId.
*/
internal init(entryIds: [String], entryIDToPlay: String?, authorizationToken: String?, onErrors: (([PlaybackAPIError]) -> Void)?) {
self.entryIds = entryIds
Expand All @@ -58,8 +60,9 @@ internal struct PlaybackUIView: View {
Initializes the `PlaybackUIView` with the provided list of entry ID and authorization token.

- Parameters:
- entryId: An entry ID of the video to be played.
- authorizationToken: Optional authorization token if required to fetch the video details.
- entryId: An entry ID of the video to be played.
- authorizationToken: Optional authorization token if required to fetch the video details.
- onError: Return potential playback errors that may occur during the loading process.
*/
internal init(entryId: String, authorizationToken: String?, onError: ((PlaybackAPIError) -> Void)?) {
self.entryIds = [entryId]
Expand Down Expand Up @@ -101,12 +104,16 @@ internal struct PlaybackUIView: View {
*/
private func loadHLSStream() {

//TO-DO Fetch all HLS urls from the entryID array
PlaybackSDKManager.shared.loadAllHLSStream(forEntryIds: entryIds, andAuthorizationToken: authorizationToken) { result in
switch result {
case .success(let videoDetails):
DispatchQueue.main.async {
self.videoDetails = videoDetails.0
self.videoDetails = []
for details in videoDetails.0 {
if let videoDetails = details.toVideoDetails() {
self.videoDetails?.append(videoDetails)
}
}
self.playlistErrors = videoDetails.1
self.hasFetchedVideoDetails = true
if (!(self.playlistErrors?.isEmpty ?? false)) {
Expand Down