@@ -1262,7 +1262,7 @@ func TestPatternGeneration(t *testing.T) {
12621262 }
12631263}
12641264
1265- // TestPatternConsistency tests that the same pattern is generated regardless of sliding window position
1265+ // TestPatternConsistency tests that the same pattern is generated for both $Time$ and $Number$ addressing modes
12661266func TestPatternConsistency (t * testing.T ) {
12671267 vodFS := os .DirFS ("testdata/assets" )
12681268 tmpDir := t .TempDir ()
@@ -1275,19 +1275,246 @@ func TestPatternConsistency(t *testing.T) {
12751275 require .True (t , ok )
12761276
12771277 // Test with different nowMS values to get different sliding windows
1278- // For a mulitple of 8 seconds, the pE should be 0, and then increase for each 2s step.
1279- // For nowMS = 800000 , the first segemnt start with tsbd=30s is 768s, which is a multiple of 8s.
1278+ // For a multiple of 8 seconds, the pE should be 0, and then increase for each 2s step.
1279+ // For nowMS = 8000000 , the first segment start with tsbd=30s is 768s, which is a multiple of 8s.
12801280 testTimes := []int {8000000 , 802000 , 804000 , 806000 }
1281- var previousPattern * m.PatternType
12821281
1283- for _ , nowMS := range testTimes {
1284- t .Run (fmt .Sprintf ("nowMS_%d" , nowMS ), func (t * testing.T ) {
1285- cfg := NewResponseConfig ()
1286- cfg .SegTimelineMode = SegTimelineModePattern
1287- cfg .TimeShiftBufferDepthS = Ptr (30 )
1282+ // Test both $Time$ and $Number$ modes
1283+ modes := []struct {
1284+ name string
1285+ mode SegTimelineMode
1286+ expectedMedia string
1287+ shouldHaveStartNr bool
1288+ }{
1289+ {
1290+ name : "$Time$" ,
1291+ mode : SegTimelineModePattern ,
1292+ expectedMedia : "$RepresentationID$/$Time$.m4s" ,
1293+ shouldHaveStartNr : false ,
1294+ },
1295+ {
1296+ name : "$Number$" ,
1297+ mode : SegTimelineModeNrPattern ,
1298+ expectedMedia : "$RepresentationID$/$Number$.m4s" ,
1299+ shouldHaveStartNr : true ,
1300+ },
1301+ }
1302+
1303+ // Store patterns from $Time$ mode to compare with $Number$ mode
1304+ var timePatterns []* m.PatternType
1305+ var timePEs []uint32
1306+ var timeSegmentTimelines []* m.SegmentTimelineType
1307+
1308+ for modeIdx , modeTest := range modes {
1309+ t .Run (modeTest .name , func (t * testing.T ) {
1310+ for timeIdx , nowMS := range testTimes {
1311+ t .Run (fmt .Sprintf ("nowMS_%d" , nowMS ), func (t * testing.T ) {
1312+ cfg := NewResponseConfig ()
1313+ cfg .SegTimelineMode = modeTest .mode
1314+ cfg .TimeShiftBufferDepthS = Ptr (30 )
1315+
1316+ liveMPD , err := LiveMPD (asset , "Manifest.mpd" , cfg , nil , nowMS )
1317+ require .NoError (t , err )
1318+
1319+ // Find audio adaptation set
1320+ var audioAS * m.AdaptationSetType
1321+ for _ , as := range liveMPD .Periods [0 ].AdaptationSets {
1322+ if as .ContentType == "audio" {
1323+ audioAS = as
1324+ break
1325+ }
1326+ }
1327+ require .NotNil (t , audioAS , "Should have audio adaptation set" )
1328+
1329+ // Verify media template
1330+ assert .Equal (t , modeTest .expectedMedia , audioAS .SegmentTemplate .Media ,
1331+ "Media template should match expected for %s mode" , modeTest .name )
1332+
1333+ // Verify startNumber
1334+ if modeTest .shouldHaveStartNr {
1335+ require .NotNil (t , audioAS .SegmentTemplate .StartNumber ,
1336+ "StartNumber should be set for $Number$ mode" )
1337+ }
1338+
1339+ stl := audioAS .SegmentTemplate .SegmentTimeline
1340+ require .NotNil (t , stl , "Should have SegmentTimeline" )
1341+ require .NotNil (t , stl .Pattern , "Should have Pattern" )
1342+ require .Len (t , stl .Pattern , 1 , "Should have exactly one Pattern" )
1343+
1344+ pattern := stl .Pattern [0 ]
12881345
1289- liveMPD , err := LiveMPD (asset , "Manifest.mpd" , cfg , nil , nowMS )
1346+ // Verify the pattern starts with the longest duration
1347+ if len (pattern .P ) > 0 {
1348+ maxDur := pattern .P [0 ].D
1349+ for _ , p := range pattern .P {
1350+ assert .LessOrEqual (t , p .D , maxDur , "First duration should be the maximum" )
1351+ }
1352+ }
1353+
1354+ // Check that S element has proper PE value
1355+ require .NotNil (t , stl .S , "Should have S elements" )
1356+ require .Greater (t , len (stl .S ), 0 , "Should have at least one S element" )
1357+ s := stl .S [0 ]
1358+ require .NotNil (t , s .PE , "Should have PE value" )
1359+ require .NotNil (t , s .T , "S element should have T attribute (same for both modes)" )
1360+
1361+ // Calculate expected PE value based on nowMS
1362+ var expectedPE int
1363+ switch nowMS {
1364+ case 8000000 :
1365+ expectedPE = 0
1366+ case 802000 :
1367+ expectedPE = 1
1368+ case 804000 :
1369+ expectedPE = 2
1370+ case 806000 :
1371+ expectedPE = 3
1372+ }
1373+
1374+ assert .Equal (t , expectedPE , int (* s .PE ),
1375+ fmt .Sprintf ("PE value should match expected based on first segment position (nowMS=%d, actual PE=%d)" ,
1376+ nowMS , int (* s .PE )))
1377+
1378+ // PE should be between 0 and pattern length - 1
1379+ patternLen := 0
1380+ for _ , p := range pattern .P {
1381+ patternLen += int (p .R ) + 1
1382+ }
1383+ assert .GreaterOrEqual (t , int (* s .PE ), 0 , "PE should be >= 0" )
1384+ assert .Less (t , int (* s .PE ), patternLen , "PE should be < pattern length" )
1385+
1386+ // Verify EssentialProperty is present
1387+ hasPatternProperty := false
1388+ for _ , prop := range audioAS .EssentialProperties {
1389+ if prop .SchemeIdUri == "urn:mpeg:dash:pattern:2024" {
1390+ hasPatternProperty = true
1391+ break
1392+ }
1393+ }
1394+ assert .True (t , hasPatternProperty , "Should have EssentialProperty for pattern support" )
1395+
1396+ // Store patterns from $Time$ mode for comparison
1397+ if modeIdx == 0 {
1398+ timePatterns = append (timePatterns , pattern )
1399+ timePEs = append (timePEs , * s .PE )
1400+ timeSegmentTimelines = append (timeSegmentTimelines , stl )
1401+ } else {
1402+ // Compare $Number$ mode with $Time$ mode
1403+ timePattern := timePatterns [timeIdx ]
1404+ assert .Equal (t , len (timePattern .P ), len (pattern .P ),
1405+ "Pattern length should be identical for both modes" )
1406+ for i := range pattern .P {
1407+ assert .Equal (t , timePattern .P [i ].D , pattern .P [i ].D ,
1408+ "Pattern durations should be identical for both modes" )
1409+ assert .Equal (t , timePattern .P [i ].R , pattern .P [i ].R ,
1410+ "Pattern repetitions should be identical for both modes" )
1411+ }
1412+
1413+ // PE values should be identical
1414+ assert .Equal (t , timePEs [timeIdx ], * s .PE ,
1415+ "PE values should be identical for both modes" )
1416+
1417+ // SegmentTimeline S elements should be identical (same T, D, R, p, pE)
1418+ timeSTL := timeSegmentTimelines [timeIdx ]
1419+ require .Equal (t , len (timeSTL .S ), len (stl .S ),
1420+ "Number of S elements should be identical" )
1421+ for i := range stl .S {
1422+ assert .Equal (t , timeSTL .S [i ].T , stl .S [i ].T ,
1423+ "S element T should be identical for both modes" )
1424+ assert .Equal (t , timeSTL .S [i ].D , stl .S [i ].D ,
1425+ "S element D should be identical for both modes" )
1426+ assert .Equal (t , timeSTL .S [i ].R , stl .S [i ].R ,
1427+ "S element R should be identical for both modes" )
1428+ assert .Equal (t , timeSTL .S [i ].P , stl .S [i ].P ,
1429+ "S element p should be identical for both modes" )
1430+ assert .Equal (t , timeSTL .S [i ].PE , stl .S [i ].PE ,
1431+ "S element pE should be identical for both modes" )
1432+ }
1433+ }
1434+ })
1435+ }
1436+ })
1437+ }
1438+ }
1439+
1440+ // TestURLParsingForNrPattern tests that segtimelinenr_pattern/ URL parameter is correctly parsed
1441+ func TestURLParsingForNrPattern (t * testing.T ) {
1442+ testCases := []struct {
1443+ url string
1444+ expectedMode SegTimelineMode
1445+ }{
1446+ {
1447+ url : "/livesim2/segtimelinenr_pattern/testpic_2s/Manifest.mpd" ,
1448+ expectedMode : SegTimelineModeNrPattern ,
1449+ },
1450+ {
1451+ url : "/livesim2/segtimeline_pattern/testpic_2s/Manifest.mpd" ,
1452+ expectedMode : SegTimelineModePattern ,
1453+ },
1454+ {
1455+ url : "/livesim2/segtimeline_time/testpic_2s/Manifest.mpd" ,
1456+ expectedMode : SegTimelineModeTime ,
1457+ },
1458+ }
1459+
1460+ for _ , tc := range testCases {
1461+ t .Run (tc .url , func (t * testing.T ) {
1462+ cfg , err := processURLCfg (tc .url , 1000000 )
12901463 require .NoError (t , err )
1464+ assert .Equal (t , tc .expectedMode , cfg .SegTimelineMode , "SegTimelineMode should match" )
1465+ })
1466+ }
1467+ }
1468+
1469+ // TestURLToMPDWithPattern tests end-to-end URL to MPD generation with Pattern
1470+ func TestURLToMPDWithPattern (t * testing.T ) {
1471+ vodFS := os .DirFS ("testdata/assets" )
1472+ tmpDir := t .TempDir ()
1473+ am := newAssetMgr (vodFS , tmpDir , false )
1474+ logger := slog .Default ()
1475+ err := am .discoverAssets (logger )
1476+ require .NoError (t , err )
1477+
1478+ testCases := []struct {
1479+ url string
1480+ nowMS int
1481+ expectedMedia string
1482+ shouldHavePattern bool
1483+ description string
1484+ }{
1485+ {
1486+ url : "/livesim2/segtimelinenr_pattern/testpic_2s/Manifest.mpd" ,
1487+ nowMS : 8000000 ,
1488+ expectedMedia : "$RepresentationID$/$Number$.m4s" ,
1489+ shouldHavePattern : true ,
1490+ description : "segtimelinenr_pattern should use $Number$ and Pattern" ,
1491+ },
1492+ {
1493+ url : "/livesim2/segtimeline_pattern/testpic_2s/Manifest.mpd" ,
1494+ nowMS : 8000000 ,
1495+ expectedMedia : "$RepresentationID$/$Time$.m4s" ,
1496+ shouldHavePattern : true ,
1497+ description : "segtimeline_pattern should use $Time$ and Pattern" ,
1498+ },
1499+ }
1500+
1501+ for _ , tc := range testCases {
1502+ t .Run (tc .description , func (t * testing.T ) {
1503+ // Parse URL configuration
1504+ cfg , err := processURLCfg (tc .url , tc .nowMS )
1505+ require .NoError (t , err )
1506+
1507+ // Find asset
1508+ contentPart := cfg .URLContentPart ()
1509+ asset , ok := am .findAsset (contentPart )
1510+ require .True (t , ok , "Should find asset" )
1511+
1512+ // Extract MPD name
1513+ _ , mpdName := path .Split (contentPart )
1514+
1515+ // Generate live MPD
1516+ liveMPD , err := LiveMPD (asset , mpdName , cfg , nil , tc .nowMS )
1517+ require .NoError (t , err , "LiveMPD should succeed" )
12911518
12921519 // Find audio adaptation set
12931520 var audioAS * m.AdaptationSetType
@@ -1299,64 +1526,36 @@ func TestPatternConsistency(t *testing.T) {
12991526 }
13001527 require .NotNil (t , audioAS , "Should have audio adaptation set" )
13011528
1302- stl := audioAS . SegmentTemplate . SegmentTimeline
1303- require . NotNil (t , stl , "Should have SegmentTimeline" )
1304- require . NotNil ( t , stl . Pattern , "Should have Pattern " )
1305- require . Len ( t , stl . Pattern , 1 , "Should have exactly one Pattern" )
1306-
1307- pattern := stl . Pattern [ 0 ]
1308-
1309- // The pattern should always be the same canonical pattern
1310- if previousPattern != nil {
1311- assert . Equal (t , len ( previousPattern . P ), len ( pattern . P ) , "Pattern length should be consistent " )
1312- for i := range pattern . P {
1313- assert . Equal (t , previousPattern . P [ i ]. D , pattern . P [ i ]. D , "Pattern durations should match " )
1314- assert . Equal (t , previousPattern . P [ i ]. R , pattern . P [ i ]. R , "Pattern repetitions should match " )
1315- }
1316- }
1317-
1318- // Verify the pattern starts with the longest duration
1319- if len ( pattern . P ) > 0 {
1320- maxDur := pattern . P [ 0 ]. D
1321- for _ , p := range pattern . P {
1322- assert . LessOrEqual ( t , p . D , maxDur , "First duration should be the maximum" )
1529+ // Verify media template
1530+ assert . Equal (t , tc . expectedMedia , audioAS . SegmentTemplate . Media ,
1531+ "Media template should match expected " )
1532+
1533+ // Verify Pattern
1534+ if tc . shouldHavePattern {
1535+ stl := audioAS . SegmentTemplate . SegmentTimeline
1536+ require . NotNil ( t , stl , "Should have SegmentTimeline" )
1537+ require . NotNil ( t , stl . Pattern , "Should have Pattern" )
1538+ require . Len (t , stl . Pattern , 1 , "Should have exactly one Pattern " )
1539+ require . NotNil ( t , stl . S , "Should have S elements" )
1540+ require . Greater (t , len ( stl . S ), 0 , "Should have at least one S element " )
1541+ require . NotNil (t , stl . S [ 0 ]. PE , "Should have PE value " )
1542+
1543+ // Verify EssentialProperty for pattern support
1544+ hasPatternProperty := false
1545+ for _ , prop := range audioAS . EssentialProperties {
1546+ if prop . SchemeIdUri == "urn:mpeg:dash:pattern:2024" {
1547+ hasPatternProperty = true
1548+ break
1549+ }
13231550 }
1551+ assert .True (t , hasPatternProperty , "Should have EssentialProperty for pattern support" )
13241552 }
13251553
1326- previousPattern = pattern
1327-
1328- // Check that S element has proper PE value
1329- require .NotNil (t , stl .S , "Should have S elements" )
1330- require .Greater (t , len (stl .S ), 0 , "Should have at least one S element" )
1331- s := stl .S [0 ]
1332- require .NotNil (t , s .PE , "Should have PE value" )
1333-
1334- // Calculate expected PE value based on nowMS
1335- // For testpic_2s: tsbd=30s, audio segments are ~2s long following video segments
1336- // PE should be based on segment number modulo 4 (pattern length)
1337- var expectedPE int
1338- switch nowMS {
1339- case 8000000 : // 800000 -> first segment at 768s -> segment 384 -> 384%4 = 0 -> PE = 0
1340- expectedPE = 0
1341- case 802000 : // 802000 -> first segment at 770s -> segment 385 -> 385%4 = 1 -> PE = 1
1342- expectedPE = 1
1343- case 804000 : // 804000 -> first segment at 772s -> segment 386 -> 386%4 = 2 -> PE = 2
1344- expectedPE = 2
1345- case 806000 : // 806000 -> first segment at 774s -> segment 387 -> 387%4 = 3 -> PE = 3
1346- expectedPE = 3
1347- }
1348-
1349- assert .Equal (t , expectedPE , int (* s .PE ),
1350- fmt .Sprintf ("PE value should match expected based on first segment position (nowMS=%d, actual PE=%d)" ,
1351- nowMS , int (* s .PE )))
1352-
1353- // PE should be between 0 and pattern length - 1
1354- patternLen := 0
1355- for _ , p := range pattern .P {
1356- patternLen += int (p .R ) + 1
1554+ // For $Number$ mode, verify startNumber is set
1555+ if strings .Contains (tc .expectedMedia , "$Number$" ) {
1556+ require .NotNil (t , audioAS .SegmentTemplate .StartNumber ,
1557+ "StartNumber should be set for $Number$ mode" )
13571558 }
1358- assert .GreaterOrEqual (t , int (* s .PE ), 0 , "PE should be >= 0" )
1359- assert .Less (t , int (* s .PE ), patternLen , "PE should be < pattern length" )
13601559 })
13611560 }
13621561}
0 commit comments