Compare commits

...
Author SHA1 Message Date
Jason Dove 7d24701a82 update changelog for release 49 [no docker] 2021-07-11 18:00:50 -05:00
Jason DoveandGitHub 286580d5aa more sorting fixes (#299) 2021-07-11 14:40:31 -05:00
Jason DoveandGitHub d9457c01e5 add special case to multiple playout mode (#298) 2021-07-11 14:25:42 -05:00
Jason DoveandGitHub 22cf759a29 sorting fixes (#297)
* sorting fixes

* use natural sort for add to schedule dialog
2021-07-11 13:20:42 -05:00
Jason DoveandGitHub 0733a3d8d7 fix loading playout anchors (#296) 2021-07-11 10:47:25 -05:00
Jason DoveandGitHub 5f28707cce include audio language metadata in all streaming modes (#295)
* include audio language metadata in all streaming modes

* cleanup
2021-07-10 06:16:46 -05:00
Jason DoveandGitHub 45f1c6b22a properly flood with fixed start time (#294) 2021-07-09 14:16:24 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3bed81aee9 Bump MudBlazor from 5.0.14 to 5.0.15 (#292)
Bumps [MudBlazor](https://github.com/Garderoben/MudBlazor) from 5.0.14 to 5.0.15.
- [Release notes](https://github.com/Garderoben/MudBlazor/releases)
- [Changelog](https://github.com/Garderoben/MudBlazor/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/Garderoben/MudBlazor/compare/v5.0.14...v5.0.15)

---
updated-dependencies:
- dependency-name: MudBlazor
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-09 13:49:52 -05:00
Jason DoveandGitHub f2eda3033c update dependencies (#289) 2021-06-24 06:35:50 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8ce989c3c9 Bump Microsoft.VisualStudio.Threading.Analyzers from 16.9.60 to 16.10.56 (#282)
Bumps [Microsoft.VisualStudio.Threading.Analyzers](https://github.com/microsoft/vs-threading) from 16.9.60 to 16.10.56.
- [Release notes](https://github.com/microsoft/vs-threading/releases)
- [Commits](https://github.com/microsoft/vs-threading/compare/v16.9.60...v16.10.56)

---
updated-dependencies:
- dependency-name: Microsoft.VisualStudio.Threading.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 13:06:41 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b5ba0dff27 Bump Blazored.LocalStorage from 4.1.1 to 4.1.2 (#286)
Bumps [Blazored.LocalStorage](https://github.com/Blazored/LocalStorage) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/Blazored/LocalStorage/releases)
- [Commits](https://github.com/Blazored/LocalStorage/compare/v4.1.1...v4.1.2)

---
updated-dependencies:
- dependency-name: Blazored.LocalStorage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 13:06:30 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
de3e2ea754 Bump NUnit3TestAdapter from 3.17.0 to 4.0.0 (#283)
Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.17.0 to 4.0.0.
- [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases)
- [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.17...V4.0.0)

---
updated-dependencies:
- dependency-name: NUnit3TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 05:44:05 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2ac840b4bd Bump Markdig from 0.24.0 to 0.25.0 (#284)
Bumps [Markdig](https://github.com/xoofx/markdig) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/xoofx/markdig/releases)
- [Changelog](https://github.com/xoofx/markdig/blob/master/changelog.md)
- [Commits](https://github.com/xoofx/markdig/compare/0.24.0...0.25.0)

---
updated-dependencies:
- dependency-name: Markdig
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 05:43:53 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c8ccb5b0a0 Bump Microsoft.NET.Test.Sdk from 16.9.4 to 16.10.0 (#285)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.9.4 to 16.10.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/compare/v16.9.4...v16.10.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 05:43:42 -05:00
23 changed files with 6366 additions and 35 deletions
+17 -1
View File
@@ -5,6 +5,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [0.0.49-prealpha] - 2021-07-11
### Added
- Include audio language metadata in all streaming modes
- Add special zero-count case to `Multiple` playout mode
- This configuration will automatically maintain the multiple count so that it is equal to the number of items in the collection
- This configuration should be used if you want to play every item in a collection exactly once before advancing
### Changed
- Use case-insensitive sorting for collections page and `Add to Collection` dialog
- Use case-insensitive sorting for all collection lists in schedule items editor
- Use natural sorting for schedules page and `Add to Schedule` dialog
### Fixed
- Fix flooding schedule items that have a fixed start time
## [0.0.48-prealpha] - 2021-06-22
### Added
- Store pixel format with media statistics; this is needed to support normalization of 10-bit media items
@@ -482,7 +497,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Initial release to facilitate testing outside of Docker.
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.48-prealpha...HEAD
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.49-prealpha...HEAD
[0.0.49-prealpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.48-prealpha...v0.0.49-prealpha
[0.0.48-prealpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.47-prealpha...v0.0.48-prealpha
[0.0.47-prealpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.46-prealpha...v0.0.47-prealpha
[0.0.46-prealpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.45-prealpha...v0.0.46-prealpha
@@ -13,7 +13,7 @@
</PackageReference>
<PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.10.56">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -33,6 +33,7 @@ namespace ErsatzTV.Application.MediaCollections.Queries
List<MediaCollectionViewModel> page = await dbContext.Collections.FromSqlRaw(
@"SELECT * FROM Collection
ORDER BY Name
COLLATE NOCASE
LIMIT {0} OFFSET {1}",
request.PageSize,
request.PageNum * request.PageSize)
@@ -30,9 +30,9 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
case PlayoutMode.One:
break;
case PlayoutMode.Multiple:
if (item.MultipleCount.GetValueOrDefault() <= 0)
if (item.MultipleCount.GetValueOrDefault() < 0)
{
return BaseError.New("[MultipleCount] must be greater than 0 for playout mode 'multiple'");
return BaseError.New("[MultipleCount] must be greater than or equal to 0 for playout mode 'multiple'");
}
break;
@@ -13,14 +13,14 @@
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="LanguageExt.Core" Version="3.4.15" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
@@ -458,6 +458,182 @@ namespace ErsatzTV.Core.Tests.Scheduling
result.Items[5].MediaItemId.Should().Be(2);
}
[Test]
public async Task FloodContent_Should_FloodWithFixedStartTime()
{
var floodCollection = new Collection
{
Id = 1,
Name = "Flood Items",
MediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
}
};
var fixedCollection = new Collection
{
Id = 2,
Name = "Fixed Items",
MediaItems = new List<MediaItem>
{
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)),
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2))
}
};
var fakeRepository = new FakeMediaCollectionRepository(
Map(
(floodCollection.Id, floodCollection.MediaItems.ToList()),
(fixedCollection.Id, fixedCollection.MediaItems.ToList())));
var items = new List<ProgramScheduleItem>
{
new ProgramScheduleItemFlood
{
Index = 1,
Collection = floodCollection,
CollectionId = floodCollection.Id,
StartTime = TimeSpan.FromHours(7)
},
new ProgramScheduleItemOne
{
Index = 2,
Collection = fixedCollection,
CollectionId = fixedCollection.Id,
StartTime = TimeSpan.FromHours(12)
}
};
var playout = new Playout
{
ProgramSchedule = new ProgramSchedule
{
Items = items,
MediaCollectionPlaybackOrder = PlaybackOrder.Chronological
},
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }
};
var televisionRepo = new FakeTelevisionRepository();
var artistRepo = new Mock<IArtistRepository>();
var builder = new PlayoutBuilder(fakeRepository, televisionRepo, artistRepo.Object, _logger);
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(24);
Playout result = await builder.BuildPlayoutItems(playout, start, finish);
result.Items.Count.Should().Be(6);
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(7));
result.Items[0].MediaItemId.Should().Be(1);
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(8));
result.Items[1].MediaItemId.Should().Be(2);
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(9));
result.Items[2].MediaItemId.Should().Be(1);
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(10));
result.Items[3].MediaItemId.Should().Be(2);
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(11));
result.Items[4].MediaItemId.Should().Be(1);
result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12));
result.Items[5].MediaItemId.Should().Be(3);
}
[Test]
public async Task FloodContent_Should_FloodWithFixedStartTime_FromAnchor()
{
var floodCollection = new Collection
{
Id = 1,
Name = "Flood Items",
MediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
}
};
var fixedCollection = new Collection
{
Id = 2,
Name = "Fixed Items",
MediaItems = new List<MediaItem>
{
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)),
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2))
}
};
var fakeRepository = new FakeMediaCollectionRepository(
Map(
(floodCollection.Id, floodCollection.MediaItems.ToList()),
(fixedCollection.Id, fixedCollection.MediaItems.ToList())));
var items = new List<ProgramScheduleItem>
{
new ProgramScheduleItemFlood
{
Index = 1,
Collection = floodCollection,
CollectionId = floodCollection.Id,
StartTime = TimeSpan.FromHours(7)
},
new ProgramScheduleItemOne
{
Index = 2,
Collection = fixedCollection,
CollectionId = fixedCollection.Id,
StartTime = TimeSpan.FromHours(12)
}
};
var playout = new Playout
{
ProgramSchedule = new ProgramSchedule
{
Items = items,
MediaCollectionPlaybackOrder = PlaybackOrder.Chronological
},
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
Anchor = new PlayoutAnchor
{
NextStart = HoursAfterMidnight(9).UtcDateTime,
NextScheduleItem = items[0],
NextScheduleItemId = 1,
InFlood = true
}
};
var televisionRepo = new FakeTelevisionRepository();
var artistRepo = new Mock<IArtistRepository>();
var builder = new PlayoutBuilder(fakeRepository, televisionRepo, artistRepo.Object, _logger);
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(32);
Playout result = await builder.BuildPlayoutItems(playout, start, finish);
result.Items.Count.Should().Be(5);
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(9));
result.Items[0].MediaItemId.Should().Be(1);
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(10));
result.Items[1].MediaItemId.Should().Be(2);
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(11));
result.Items[2].MediaItemId.Should().Be(1);
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12));
result.Items[3].MediaItemId.Should().Be(3);
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(7));
result.Items[4].MediaItemId.Should().Be(2);
result.Anchor.InFlood.Should().BeTrue();
}
[Test]
public async Task FloodContent_Should_FloodAroundFixedContent_DurationWithoutOfflineTail()
{
@@ -729,6 +905,96 @@ namespace ErsatzTV.Core.Tests.Scheduling
result.Anchor.MultipleRemaining.Should().Be(1);
}
[Test]
public async Task Auto_Zero_MultipleCount()
{
var collectionOne = new Collection
{
Id = 1,
Name = "Multiple Items 1",
MediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(3, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))
}
};
var collectionTwo = new Collection
{
Id = 2,
Name = "Multiple Items 2",
MediaItems = new List<MediaItem>
{
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(5, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
}
};
var fakeRepository = new FakeMediaCollectionRepository(
Map(
(collectionOne.Id, collectionOne.MediaItems.ToList()),
(collectionTwo.Id, collectionTwo.MediaItems.ToList())));
var items = new List<ProgramScheduleItem>
{
new ProgramScheduleItemMultiple
{
Id = 1,
Index = 1,
Collection = collectionOne,
CollectionId = collectionOne.Id,
StartTime = null,
Count = 0
},
new ProgramScheduleItemMultiple
{
Id = 2,
Index = 2,
Collection = collectionTwo,
CollectionId = collectionTwo.Id,
StartTime = null,
Count = 0
}
};
var playout = new Playout
{
ProgramSchedule = new ProgramSchedule
{
Items = items,
MediaCollectionPlaybackOrder = PlaybackOrder.Chronological
},
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
};
var televisionRepo = new FakeTelevisionRepository();
var artistRepo = new Mock<IArtistRepository>();
var builder = new PlayoutBuilder(fakeRepository, televisionRepo, artistRepo.Object, _logger);
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(5);
Playout result = await builder.BuildPlayoutItems(playout, start, finish);
result.Items.Count.Should().Be(5);
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0));
result.Items[0].MediaItemId.Should().Be(1);
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1));
result.Items[1].MediaItemId.Should().Be(2);
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2));
result.Items[2].MediaItemId.Should().Be(3);
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3));
result.Items[3].MediaItemId.Should().Be(4);
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4));
result.Items[4].MediaItemId.Should().Be(5);
result.Anchor.NextScheduleItem.Should().Be(items[0]);
result.Anchor.MultipleRemaining.Should().BeNull();
}
[Test]
public async Task Alternating_Duration_Should_Maintain_Duration()
{
+1
View File
@@ -13,6 +13,7 @@ namespace ErsatzTV.Core.Domain
public DateTime NextStart { get; set; }
public int? MultipleRemaining { get; set; }
public DateTime? DurationFinish { get; set; }
public bool InFlood { get; set; }
public DateTimeOffset NextStartOffset => new DateTimeOffset(NextStart, TimeSpan.Zero).ToLocalTime();
+1 -1
View File
@@ -16,7 +16,7 @@
<PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.10.56">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
+16 -2
View File
@@ -230,14 +230,29 @@ namespace ErsatzTV.Core.FFmpeg
return this;
}
public FFmpegProcessBuilder WithMetadata(Channel channel)
public FFmpegProcessBuilder WithMetadata(Channel channel, Option<MediaStream> maybeAudioStream)
{
if (channel.StreamingMode == StreamingMode.TransportStream)
{
_arguments.AddRange(new[] { "-map_metadata", "-1" });
}
foreach (MediaStream audioStream in maybeAudioStream)
{
if (!string.IsNullOrWhiteSpace(audioStream.Language))
{
_arguments.AddRange(new[] { "-metadata:s:a:0", $"language={audioStream.Language}" });
}
}
var arguments = new List<string>
{
"-metadata", "service_provider=\"ErsatzTV\"",
"-metadata", $"service_name=\"{channel.Name}\""
};
_arguments.AddRange(arguments);
return this;
}
@@ -323,7 +338,6 @@ namespace ErsatzTV.Core.FFmpeg
new[]
{
"-c:a", playbackSettings.AudioCodec,
"-map_metadata", "-1",
"-movflags", "+faststart",
"-muxdelay", "0",
"-muxpreload", "0"
+3 -3
View File
@@ -105,7 +105,7 @@ namespace ErsatzTV.Core.FFmpeg
});
return builder.WithPlaybackArgs(playbackSettings)
.WithMetadata(channel)
.WithMetadata(channel, maybeAudioStream)
.WithFormat("mpegts")
.WithDuration(start + version.Duration - now)
.WithPipe()
@@ -130,7 +130,7 @@ namespace ErsatzTV.Core.FFmpeg
.WithErrorText(desiredResolution, errorMessage)
.WithPixfmt("yuv420p")
.WithPlaybackArgs(playbackSettings)
.WithMetadata(channel)
.WithMetadata(channel, None)
.WithFormat("mpegts");
duration.IfSome(d => builder = builder.WithDuration(d));
@@ -149,7 +149,7 @@ namespace ErsatzTV.Core.FFmpeg
.WithRealtimeOutput(playbackSettings.RealtimeOutput)
.WithInfiniteLoop()
.WithConcat($"http://localhost:{Settings.ListenPort}/ffmpeg/concat/{channel.Number}")
.WithMetadata(channel)
.WithMetadata(channel, None)
.WithFormat("mpegts")
.WithPipe()
.Build();
+24 -5
View File
@@ -201,6 +201,7 @@ namespace ErsatzTV.Core.Scheduling
// start with the previous multiple/duration states
Option<int> multipleRemaining = Optional(startAnchor.MultipleRemaining);
Option<DateTimeOffset> durationFinish = startAnchor.DurationFinishOffset;
bool inFlood = startAnchor.InFlood;
bool customGroup = multipleRemaining.IsSome || durationFinish.IsSome;
@@ -215,7 +216,8 @@ namespace ErsatzTV.Core.Scheduling
scheduleItem,
currentTime,
multipleRemaining.IsSome,
durationFinish.IsSome);
durationFinish.IsSome,
inFlood);
IMediaCollectionEnumerator enumerator = collectionEnumerators[CollectionKeyForItem(scheduleItem)];
await enumerator.Current.IfSomeAsync(
@@ -268,7 +270,16 @@ namespace ErsatzTV.Core.Scheduling
case ProgramScheduleItemMultiple multiple:
if (multipleRemaining.IsNone)
{
multipleRemaining = multiple.Count;
if (multiple.Count == 0)
{
multipleRemaining = collectionMediaItems[CollectionKeyForItem(scheduleItem)]
.Count;
}
else
{
multipleRemaining = multiple.Count;
}
customGroup = true;
}
@@ -318,6 +329,11 @@ namespace ErsatzTV.Core.Scheduling
"Flood");
index++;
customGroup = false;
inFlood = false;
}
else
{
inFlood = true;
}
});
break;
@@ -374,7 +390,8 @@ namespace ErsatzTV.Core.Scheduling
NextScheduleItemId = nextScheduleItem.Id,
NextStart = GetStartTimeAfter(nextScheduleItem, currentTime).UtcDateTime,
MultipleRemaining = multipleRemaining.IsSome ? multipleRemaining.ValueUnsafe() : null,
DurationFinish = durationFinish.IsSome ? durationFinish.ValueUnsafe().UtcDateTime : null
DurationFinish = durationFinish.IsSome ? durationFinish.ValueUnsafe().UtcDateTime : null,
InFlood = inFlood
};
// build program schedule anchors
@@ -419,13 +436,15 @@ namespace ErsatzTV.Core.Scheduling
ProgramScheduleItem item,
DateTimeOffset start,
bool inMultiple = false,
bool inDuration = false)
bool inDuration = false,
bool inFlood = false)
{
switch (item.StartType)
{
case StartType.Fixed:
if (item is ProgramScheduleItemMultiple && inMultiple ||
item is ProgramScheduleItemDuration && inDuration)
item is ProgramScheduleItemDuration && inDuration ||
item is ProgramScheduleItemFlood && inFlood)
{
return start;
}
@@ -13,16 +13,16 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Dapper" Version="2.0.90" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00013" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00013" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00013" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00014" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00014" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00014" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.7" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.10.56">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_PlayoutAnchorInFlood : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Anchor_InFlood",
table: "Playout",
type: "INTEGER",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Anchor_InFlood",
table: "Playout");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Fix_PlayoutAnchorInFlood : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("UPDATE Playout SET Anchor_InFlood = 0 WHERE Anchor_InFlood IS NULL");
migrationBuilder.AlterColumn<bool>(
name: "Anchor_InFlood",
table: "Playout",
type: "INTEGER",
nullable: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
@@ -14,7 +14,7 @@ namespace ErsatzTV.Infrastructure.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.6");
.HasAnnotation("ProductVersion", "5.0.7");
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
{
@@ -2189,6 +2189,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b1.Property<DateTime?>("DurationFinish")
.HasColumnType("TEXT");
b1.Property<bool>("InFlood")
.HasColumnType("INTEGER");
b1.Property<int?>("MultipleRemaining")
.HasColumnType("INTEGER");
+5 -4
View File
@@ -17,12 +17,12 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Blazored.LocalStorage" Version="4.1.1" />
<PackageReference Include="Blazored.LocalStorage" Version="4.1.2" />
<PackageReference Include="FluentValidation" Version="10.2.3" />
<PackageReference Include="FluentValidation.AspNetCore" Version="10.2.3" />
<PackageReference Include="HtmlSanitizer" Version="5.0.404" />
<PackageReference Include="LanguageExt.Core" Version="3.4.15" />
<PackageReference Include="Markdig" Version="0.24.0" />
<PackageReference Include="Markdig" Version="0.25.0" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="3.0.1" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.7" />
@@ -30,11 +30,12 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.10.56">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MudBlazor" Version="5.0.14" />
<PackageReference Include="MudBlazor" Version="5.0.15" />
<PackageReference Include="NaturalSort.Extension" Version="3.1.0" />
<PackageReference Include="PPioli.FluentValidation.Blazor" Version="5.0.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="6.0.38" />
<PackageReference Include="Serilog" Version="2.10.0" />
+9 -5
View File
@@ -164,7 +164,7 @@
<MudText Class="mt-3 mb-2" Typo="Typo.h6">Playout Mode</MudText>
<ul class="mud-typography-body1">
<li><b>One</b> - to play one media item from the collection before advancing to the next schedule item.</li>
<li><b>Multiple</b> - to play a specified <b>count</b> of media items from the collection before advancing to the next schedule item.</li>
<li><b>Multiple</b> - to play a specified <b>count</b> of media items from the collection before advancing to the next schedule item. A count of <b>0</b> is a special case that will automatically and always equal the number of items in the collection, so each item from the collection will be played once before advancing.</li>
<li><b>Duration</b> - to play the maximum number of complete media items that will fit in the specified <b>playout duration</b>, before either going offline for the remainder of the <b>playout duration</b> (an <b>offline tail</b>), or immediately advancing to the next schedule item.</li>
<li><b>Flood</b> - to play media items from the collection forever, or until the next schedule item's <b>start time</b> if one exists.</li>
</ul>
@@ -193,10 +193,14 @@
private async Task LoadScheduleItems()
{
// TODO: fix performance
_mediaCollections = await _mediator.Send(new GetAllCollections());
_televisionShows = await _mediator.Send(new GetAllTelevisionShows());
_televisionSeasons = await _mediator.Send(new GetAllTelevisionSeasons());
_artists = await _mediator.Send(new GetAllArtists());
_mediaCollections = await _mediator.Send(new GetAllCollections())
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_televisionShows = await _mediator.Send(new GetAllTelevisionShows())
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_televisionSeasons = await _mediator.Send(new GetAllTelevisionSeasons())
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
_artists = await _mediator.Send(new GetAllArtists())
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
string name = string.Empty;
Option<ProgramScheduleViewModel> maybeSchedule = await _mediator.Send(new GetProgramScheduleById(Id));
+2 -1
View File
@@ -4,6 +4,7 @@
@using ErsatzTV.Application.ProgramSchedules.Queries
@using ErsatzTV.Application.Configuration.Queries
@using ErsatzTV.Application.Configuration.Commands
@using NaturalSort.Extension
@inject IDialogService _dialog
@inject IMediator _mediator
@@ -135,7 +136,7 @@
await _mediator.Send(new SaveConfigElementByKey(ConfigElementKey.SchedulesPageSize, state.PageSize.ToString()));
List<ProgramScheduleViewModel> schedules = await _mediator.Send(new GetAllProgramSchedules());
IOrderedEnumerable<ProgramScheduleViewModel> sorted = schedules.OrderBy(s => s.Name);
IOrderedEnumerable<ProgramScheduleViewModel> sorted = schedules.OrderBy(s => s.Name, new NaturalSortComparer(StringComparison.CurrentCultureIgnoreCase));
// TODO: properly page this data
return new TableData<ProgramScheduleViewModel>
+1 -1
View File
@@ -72,7 +72,7 @@
protected override async Task OnParametersSetAsync()
{
_collections = await _mediator.Send(new GetAllCollections())
.Map(list => new[] { _newCollection }.Append(list).ToList());
.Map(list => new[] { _newCollection }.Append(list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase)).ToList());
if (_memoryCache.TryGetValue("AddToCollectionDialog.SelectedCollectionId", out int id))
{
+3 -1
View File
@@ -1,5 +1,6 @@
@using ErsatzTV.Application.ProgramSchedules
@using ErsatzTV.Application.ProgramSchedules.Queries
@using NaturalSort.Extension
@inject IMediator _mediator
<div @onkeydown="@OnKeyDown">
@@ -49,7 +50,8 @@
private ProgramScheduleViewModel _selectedSchedule;
protected override async Task OnParametersSetAsync() =>
_schedules = await _mediator.Send(new GetAllProgramSchedules());
_schedules = await _mediator.Send(new GetAllProgramSchedules())
.Map(list => list.OrderBy(vm => vm.Name, new NaturalSortComparer(StringComparison.CurrentCultureIgnoreCase)).ToList());
private string FormatText() => $"Select the schedule to add the {EntityType} {EntityName}";
@@ -13,7 +13,7 @@ namespace ErsatzTV.Validators
() => RuleFor(i => i.StartTime).NotNull());
When(
i => i.PlayoutMode == PlayoutMode.Multiple,
() => RuleFor(i => i.MultipleCount).NotNull().GreaterThan(0));
() => RuleFor(i => i.MultipleCount).NotNull().GreaterThanOrEqualTo(0));
When(
i => i.PlayoutMode == PlayoutMode.Duration,
() => RuleFor(i => i.PlayoutDuration).NotNull());