Skip to content

Commit 3a2e41e

Browse files
authored
Merge pull request #153 from Comcast/release/1.7.0
Release 1.7.0
2 parents 07a909b + 695f3ea commit 3a2e41e

22 files changed

+1599
-69
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Generate Release
2+
run-name: Generate Release ${{ fromJSON(inputs.major) }}.${{ fromJSON(inputs.minor) }}.${{ fromJSON(inputs.patch) }}
3+
4+
on:
5+
workflow_dispatch:
6+
inputs:
7+
major:
8+
description: 'Major version'
9+
required: true
10+
default: '1'
11+
type: choice
12+
options:
13+
- '1'
14+
- '2'
15+
minor:
16+
description: 'Minor version'
17+
required: true
18+
type: number
19+
patch:
20+
description: 'Patch version'
21+
required: true
22+
type: number
23+
24+
jobs:
25+
bump-version-and-raise-pr:
26+
runs-on: ubuntu-latest
27+
permissions:
28+
contents: write
29+
pull-requests: write
30+
env:
31+
version: ${{ fromJSON(inputs.major) }}.${{ fromJSON(inputs.minor) }}.${{ fromJSON(inputs.patch) }}
32+
branch: ${{ inputs.major == 2 && 'develop' || 'develop_1.x' }}
33+
target: ${{ inputs.major == 2 && 'main' || 'main_1.x' }}
34+
steps:
35+
- name: Check minor has no decimals
36+
run: ${{ !contains(fromJSON(inputs.minor), '.') }}
37+
38+
- name: Check patch has no decimals
39+
run: ${{ !contains(fromJSON(inputs.patch), '.') }}
40+
41+
- name: Checkout ${{ env.target }}
42+
uses: actions/checkout@v4
43+
with:
44+
ref: ${{ env.target }}
45+
fetch-depth: 0
46+
47+
- name: Ensure tag does not exist
48+
run: |
49+
if git show-ref --tags --verify --quiet "refs/tags/$version"; then
50+
echo "::error::Tag $version exists" && exit 1
51+
else
52+
echo "Tag $version does not exist"
53+
fi
54+
55+
- name: Ensure branch does not exist
56+
run: |
57+
if git show-ref --verify --quiet "refs/remotes/origin/release/$version"; then
58+
echo "::error::Release branch release/$version exists" && exit 1
59+
else
60+
echo "Release branch release/$version does not exist"
61+
fi
62+
63+
- name: Merge in ${{ env.branch }}
64+
run: |
65+
git config user.name github-actions
66+
git config user.email [email protected]
67+
git merge origin/${{ env.branch }}
68+
69+
- name: Update version in txt file
70+
run: sed -i "s/[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}/$version/" ./mambaSharedFramework/Resources/version.txt
71+
72+
- name: Update version in xcodeproj file
73+
run: sed -i "s/MARKETING_VERSION = [0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\};/MARKETING_VERSION = $version;/" ./mamba.xcodeproj/project.pbxproj
74+
75+
- name: Update version in podspec file
76+
run: sed -i "s/= \"[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\"/= \"$version\"/" ./mamba.podspec
77+
78+
- name: Create pull request
79+
uses: peter-evans/create-pull-request@v7
80+
with:
81+
title: Release ${{ env.version }}
82+
commit-message: Bump version to ${{ env.version }}
83+
branch: release/${{ env.version }}
84+
body: |
85+
Automated version bump to ${{ env.version }} generated by [create-pull-request][1].
86+
87+
[1]: https://github.com/peter-evans/create-pull-request
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Publish Release
2+
on:
3+
push:
4+
branches:
5+
- 'main'
6+
- 'main_1.x'
7+
8+
jobs:
9+
get-version:
10+
name: Get version for release
11+
runs-on: ubuntu-latest
12+
outputs:
13+
version: ${{ steps.version.outputs.version }}
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
- id: version
18+
run: echo "version=`cat ./mambaSharedFramework/Resources/version.txt`" >> "$GITHUB_OUTPUT"
19+
20+
publish-release:
21+
name: Publish release ${{ needs.get-version.outputs.version }}
22+
runs-on: ubuntu-latest
23+
needs: get-version
24+
permissions:
25+
contents: write
26+
steps:
27+
- uses: actions/checkout@v4
28+
- name: Create release
29+
uses: elgohr/Github-Release-Action@v5
30+
env:
31+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32+
with:
33+
title: ${{ needs.get-version.outputs.version }}
34+
tag: ${{ needs.get-version.outputs.version }}

mamba.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22

33
s.name = "mamba"
4-
s.version = "1.6.0"
4+
s.version = "1.7.0"
55
s.license = { :type => 'Apache License, Version 2.0',
66
:text => <<-LICENSE
77
Copyright 2017 Comcast Cable Communications Management, LLC

mamba.xcodeproj/project.pbxproj

Lines changed: 94 additions & 46 deletions
Large diffs are not rendered by default.

mambaSharedFramework/HLS Models/Playlist Structure/HLSPlaylistStructure.swift

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,11 @@ final class HLSPlaylistStructure: HLSPlaylistStructureInterface {
244244
do {
245245
let result = try HLSPlaylistStructureConstructor.generateMediaGroups(fromTags: _tags)
246246

247-
let mediaSpans = try HLSPlaylistStructureConstructor.generateMediaSpans(fromTags: _tags,
248-
header: result.header,
249-
mediaSegmentGroups: result.mediaSegmentGroups)
247+
let mediaSpans = (try? HLSPlaylistStructureConstructor.generateMediaSpans(
248+
fromTags: _tags,
249+
header: result.header,
250+
mediaSegmentGroups: result.mediaSegmentGroups
251+
)) ?? []
250252

251253
self._header = result.header
252254
self._mediaSegmentGroups = result.mediaSegmentGroups
@@ -508,6 +510,11 @@ fileprivate struct HLSPlaylistStructureConstructor {
508510
header: TagGroup?,
509511
mediaSegmentGroups: [MediaSegmentTagGroup]) throws -> [TagSpan] {
510512

513+
// If the playlist contains no segments then there are no spans
514+
if mediaSegmentGroups.isEmpty {
515+
return []
516+
}
517+
511518
var mediaSpans = [TagSpan]()
512519

513520
// handle our only known spannable tag, `EXT-X-KEY`
@@ -546,7 +553,13 @@ fileprivate struct HLSPlaylistStructureConstructor {
546553

547554
if let startKeyIndex = startKeyIndex, let startKeyTag = startKeyTag {
548555
// we are closing out our last key
549-
mediaSpans.append(TagSpan(parentTag: startKeyTag, tagMediaSpan: startKeyIndex...currentIndex - 1))
556+
let spanEnd = currentIndex - 1
557+
if startKeyIndex <= spanEnd {
558+
mediaSpans.append(TagSpan(parentTag: startKeyTag, tagMediaSpan: startKeyIndex...spanEnd))
559+
} else {
560+
assertionFailure("Invalid media span range: \(startKeyIndex)...\(spanEnd)")
561+
throw ParseError.invalidMediaSpanRange(start: startKeyIndex, end: spanEnd)
562+
}
550563
}
551564

552565
startKeyIndex = currentIndex
@@ -559,16 +572,26 @@ fileprivate struct HLSPlaylistStructureConstructor {
559572

560573
// close out our last tag
561574
if let startKeyIndex = startKeyIndex, let startKeyTag = startKeyTag {
562-
mediaSpans.append(TagSpan(parentTag: startKeyTag, tagMediaSpan: startKeyIndex...(currentIndex - 1)))
575+
let spanEnd = currentIndex - 1
576+
if startKeyIndex <= spanEnd {
577+
mediaSpans.append(TagSpan(parentTag: startKeyTag, tagMediaSpan: startKeyIndex...spanEnd))
578+
} else {
579+
assertionFailure("Invalid final media span range: \(startKeyIndex)...\(spanEnd)")
580+
throw ParseError.invalidMediaSpanRange(start: startKeyIndex, end: spanEnd)
581+
}
582+
}
583+
584+
// assert if key counts mismatch (footer keys or malformed playlists)
585+
if keyCount != keyTags.count {
586+
assert(keyCount == keyTags.count, "Warning: generateMediaSpans counted \(keyCount) EXT-X-KEY tags, but found \(keyTags.count). Possibly due to footer-only key tags.")
563587
}
564-
565-
assert(keyCount == keyTags.count, "we missed a key tag")
566588

567589
return mediaSpans
568590
}
569591

570592
private enum ParseError: Error {
571593
case foundMediaSegmentWithoutDuration(inMediaSequence: MediaSequence)
594+
case invalidMediaSpanRange(start: Int, end: Int)
572595
}
573596
}
574597

mambaSharedFramework/HLS Utils/String Util/String+DateParsing.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import Foundation
2121

2222
extension String {
2323

24-
private struct DateFormatter {
24+
struct DateFormatter {
2525
static let iso8601MS: Foundation.DateFormatter = {
2626
let formatter = Foundation.DateFormatter()
2727
formatter.calendar = Calendar(identifier: Calendar.Identifier.iso8601)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//
2+
// HLSInterstitialValueTypes.swift
3+
// mamba
4+
//
5+
// Created by Migneco, Ray on 10/22/24.
6+
//
7+
8+
import Foundation
9+
10+
/// specifies how the client should align interstitial content to the primary content
11+
public struct HLSInterstitialAlignment: FailableStringLiteralConvertible, Equatable {
12+
13+
public enum Snap: String, CaseIterable {
14+
/// client SHOULD locate the segment boundary closest to the scheduled resumption point from the
15+
/// interstitial in the Media Playlist of the primary content and resume playback of primary content at that boundary.
16+
case `in` = "IN"
17+
18+
/// client SHOULD locate the segment boundary closest to the START-DATE of the interstitial in the
19+
/// Media Playlist of the primary content and transition to the interstitial at that boundary.
20+
case out = "OUT"
21+
}
22+
23+
/// the set of snap options for aligning interstitial content
24+
public let values: Set<Snap>
25+
26+
/// creates a snap guide based on provided values
27+
///
28+
/// - Parameter values: array of `Snap` values
29+
public init(values: [Snap]) {
30+
self.values = Set(values)
31+
}
32+
33+
/// creates a snap guide based on the provided string value
34+
///
35+
/// - Parameter string: a comma separated string indicating snap values
36+
public init?(string: String) {
37+
let snapValues = string.components(separatedBy: ",")
38+
.compactMap({ Snap(rawValue: $0 )})
39+
40+
guard !snapValues.isEmpty else { return nil }
41+
42+
self.init(values: snapValues)
43+
}
44+
}
45+
46+
/// specifies how the player should enforce seek restrictions for the interstitial content
47+
public struct HLSInterstitialSeekRestrictions: FailableStringLiteralConvertible, Equatable {
48+
49+
public enum Restriction: String, CaseIterable {
50+
/// If the list contains SKIP then while the interstitial is being played, the client MUST NOT
51+
/// allow the user to seek forward from the current playhead position or set the rate to
52+
/// greater than the regular playback rate until playback reaches the end of the interstitial.
53+
case skip = "SKIP"
54+
55+
/// If the list contains JUMP then the client MUST NOT allow the user to seek from a position
56+
/// in the primary asset earlier than the START-DATE attribute to a position after it without
57+
/// first playing the interstitial asset, even if the interstitial at START-DATE was played
58+
/// through earlier.
59+
case jump = "JUMP"
60+
}
61+
62+
/// set of restrictions applied to the interstitial content
63+
public let restrictions: Set<Restriction>
64+
65+
/// Creates a set of restrictions based on provided values
66+
///
67+
/// - Parameter restrictions: array of `Restriction`
68+
public init(restrictions: [Restriction]) {
69+
self.restrictions = Set(restrictions)
70+
}
71+
72+
/// creates a snap guide based on the provided string value
73+
///
74+
/// - Parameter string: a comma separated string indicating snap values
75+
public init?(string: String) {
76+
let restrictions = string.components(separatedBy: ",")
77+
.compactMap({ Restriction(rawValue: $0 )})
78+
79+
guard !restrictions.isEmpty else { return nil }
80+
81+
self.init(restrictions: restrictions)
82+
}
83+
}
84+
85+
public enum HLSInterstitialTimelineStyle: String, FailableStringLiteralConvertible {
86+
87+
/// indicates whether the interstitial is intended to be presented as distinct from the content
88+
case highlight = "HIGHLIGHT"
89+
90+
/// indicates that the interstitial should NOT be presented as differentiated from the content
91+
case primary = "PRIMARY"
92+
93+
/// Creates a timeline style from the provided string
94+
public init?(string: String) {
95+
self.init(rawValue: string)
96+
}
97+
}
98+
99+
/// Type that indicates how an interstitial event should be presented on a timeline
100+
public enum HLSInterstitialTimelineOccupation: String, FailableStringLiteralConvertible {
101+
102+
/// the interstitial should be presented as a single point on the timeline
103+
case point = "POINT"
104+
105+
/// the interstitial should be presented as a range on the timeline
106+
case range = "RANGE"
107+
108+
/// Creates a timeline occupation from the provided string
109+
public init?(string: String) {
110+
self.init(rawValue: string)
111+
}
112+
}

mambaSharedFramework/HLSValidationIssue.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ public enum IssueDescription: String {
5858
case EXT_X_STREAM_INFRenditionGroupCLOSEDCAPTIONSValidator = "EXT-X-STREAM-INF - CLOSED-CAPTIONS The value is a quoted-string or an enumerated-string NONE. If the value is a quoted-string, it MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag elsewhere in the Playlist whose TYPE attribute is CLOSED-CAPTIONS. If it is NONE, all EXT-X-STREAM-INF tags must have this attribute with a value of NONE."
5959
case EXT_X_TARGETDURATIONLengthValidator = "The EXT-X-TARGETDURATION tag specifies the maximum media segment duration. The EXTINF duration of each media segment in the Playlist file MUST be less than or equal to the target duration."
6060
case EXT_X_STARTTimeOffsetValidator = "TIME-OFFSET absolute value should never be longer than the playlist or If the variant does not contain EXT-X-ENDLIST, TIME-OFFSET should not be within 3 target durations from the end."
61+
case EXT_X_DEFINENameWithNoValue = "The VALUE attribute is REQUIRED if the EXT-X-DEFINE tag has a NAME attribute."
62+
case EXT_X_DEFINENoNameNorImportNorQueryparam = "An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a QUERYPARAM attribute."
63+
case EXT_X_DEFINEMoreThanOneOfNameImportOrQueryParam = "An EXT-X-DEFINE tag MUST only contain one of NAME, IMPORT, or QUERYPARAM attribute."
64+
case EXT_X_DEFINEDuplicateDefinition = "An EXT-X-DEFINE tag MUST NOT specify the same Variable Name as any other EXT-X-DEFINE tag in the same Playlist. Parsers that encounter duplicate Variable Name declarations MUST fail to parse the Playlist."
65+
case EXT_X_DEFINEImportInMultivariant = "EXT-X-DEFINE tags containing the IMPORT attribute MUST NOT occur in Multivariant Playlists; they are only allowed in Media Playlists."
66+
case EXT_X_DEFINENoQueryParameterValue = "If the QUERYPARAM attribute value does not match any query parameter in the URI or the matching parameter has no associated value, the parser MUST fail to parse the Playlist."
6167
case HLSPlaylistRenditionGroupAUDIOValidator = "All members of a group with TYPE=AUDIO MUST use the same audio sample format."
6268
case HLSPlaylistRenditionGroupVIDEOValidator = "All members of a group with TYPE=VIDEO MUST use the same video sample format."
6369
case EXT_X_MEDIARenditionGroupNAMEValidator = "All EXT-X-MEDIA tags in the same group MUST have different NAME attributes."
@@ -80,5 +86,7 @@ public enum IssueDescription: String {
8086
case EXT_X_DATERANGETagPLANNED_DURATIONMustNotBeNegative = "PLANNED-DURATION MUST NOT be negative."
8187
case EXT_X_DATERANGEExistsWithNoEXT_X_PROGRAM_DATE_TIME = "If a Playlist contains an EXT-X-DATERANGE tag, it MUST also contain at least one EXT-X-PROGRAM-DATE-TIME tag."
8288
case EXT_X_DATERANGEAttributeMismatchForTagsWithSameID = "If a Playlist contains two EXT-X-DATERANGE tags with the same ID attribute value, then any AttributeName that appears in both tags MUST have the same AttributeValue."
89+
case EXT_X_DATERANGEMissingAssetListOrAssetUriAttribute = "A Date Range tag specifying CLASS=com.apple.hls.interstitial must contain either an X-ASSET-LIST OR X-ASSET-URI attribute"
90+
case EXT_X_DATERANGEContainsBothAssetListAndAssetUriAttribute = "A Date Range tag specifying CLASS=com.apple.hls.interstitial cannot contain both an X-ASSET-LIST AND X-ASSET-URI attribute"
8391
}
8492

0 commit comments

Comments
 (0)