Giter VIP home page Giter VIP logo

strix-music's Introduction

Strix Music

The Strix Music App is the music player that adapts to you. Combine your libraries from multiple sources and services, and reskin the app to your taste. Or, try building with the Strix Music SDK. Seamlessly integrate any music source or Strix plugin into your app.

Open source alpha

We spent the last 2 years designing and refining the SDK, making sure it could cleanly handle anything we threw at it.

The foundation has been laid, and we've entered the next stage in development: open source alpha.

✔ Use the SDK in your own project to easily interface with any music service.

✔ Extend any strix-compatible app with new music sources and plugins.

✔ Provide feedback, feature suggestions or bug reports to improve the SDK for everyone.

❌ Build WinUI applications using our inbox control library (not ready)

❌ Download the Strix Music App, our incubation and demo project for the SDK (not ready)

The Strix Music SDK

  • Our incubation project where we define and refine the infrastructure before going cross-language.
  • Easily bring the power of Strix to your own projects.
  • Rapidly interface with any available music services.
  • Merge multiple music services together into a single, transparent API.
  • Bring new music services to the Strix ecosystem.

The Strix Music App

  • Our incubation and demo app for the Strix SDK.
  • You rely on the SDK for your apps, so we built our own app to catch bugs, test new features, and push boundaries. Dogfooding ftw!
  • Much like how Surface is Microsoft's vision for making the most of Windows, this app is our vision for making the most of the SDK.
  • Multi-service, thanks to the Strix Music SDK.
  • Multi-platform, powered by the Uno Platform.
  • Multi-skinnable, thanks to the highly refined data structure in the SDK and the MVVM architecture built on top.

The Cores: interchangeable music sources

A core is any music source implemented with our CoreModel APIs - a standardized, flexible and refined data structure designed to fit the needs of different types of music sources.

Basic core usage

// Core method signature may vary.
var localFilesCore = new LocalFilesCore(id, folderAbstraction);

// Perform any async setup needed before usage, such as login or database init.
await localFilesCore.InitAsync();

// Get tracks from the library
var tracks = await localFilesCore.Library.GetTracksAsync(limit: 20, offset: 0).ToListAsync();

Merge them together!

The Strix Music SDK allows you to merge multiple sources together. Libraries, search results, recently played, devices, etc. are all seamelessly combined under a single data structure.

// Assumes that `config` contains all the info needed to function without user interaction.
// Spotify and YouTube cores are examples only.

// Create a few cores.
var onedrive = new OneDriveCore(id, config);
var youtube = new YouTubeCore(id, config);
var sideloaded = new RemoteCore(id, config);

// Merge them together
var mergedLayer = new MergedCore(mergedConfig, onedrive, spotify, youtube, sideloaded);
await mergedLayer.InitAsync();

// Get albums in all libraries
var albums = await mergedLayer.Library.GetAlbumItemsAsync(limit: 20, offset: 0).ToListAsync();

// and play one
await mergedLayer.Library.PlayAlbumCollectionAsync(startWith: albums[5]);

// Search everywhere
var searchResults = await mergedLayer.Search.GetSearchResultsAsync("Zombie by Jamie T").ToListAsync();
var tracks = await searchResults.GetTracksAsync(limit: 100, offset: 0).ToListAsync();
var artists = await searchResults.GetArtistItemsAsync(limit: 10, offset: 0).ToListAsync();

// Get all the sources that were combined to create the track
var sourceTracks = tracks[0].Sources; // FilesCoreTrack, YouTubeTrack

The sky is the limit

Check out our docs to get started

Anyone can create a core, and a core can be anything that satisfies the CoreModel interfaces.

Here are some ideas for cores we'd like to see join the Strix ecosystem (click to expand):

  • File based (90%+ code sharing)
    • Local Files
    • OneDrive
    • Google Drive
    • Dropbox
    • IPFS
    • FTP
  • Streaming based (all possible but not promised)
    • YouTube
    • SoundCloud
    • Spotify
    • Pandora
    • Audible
    • Deezer
  • Hardware based
    • CDs
    • Zune
  • Remote (out of process / on another machine)

Model Plugins: Easy customization

Model plugins are an extremely modular and flexible way to customize the SDK.

In short, a model plugin modifies data or behavior for any AppModel in the SDK by wrapping around it and selectively overriding members, then taking the place of the original model.

Once you have at least one model plugin, use the PluginModels layer to wrap around an existing data root, and provide plugins that you want applied to all interface implementations in the data structure. For example, an ImageCollection plugin is applied to IAlbum, IArtist, IPlaylist, etc..

Then, simply take the place of the original data root.

Applying model plugins

// Create the AppModels with one or more sources
var mergedLayer = new MergedCore(mergedConfig, onedrive, spotify, youtube, sideloaded);

// Add plugins
var pluginLayer = new StrixDataRootPluginWrapper(mergedLayer,
    new FallbackImagePlugin(fallbackImage),

    // Handle playback locally, add start/stop flair, bring your own shuffle logic, whatever you want.
    new PlaybackHandlerPlugin(_playbackHandler),

    // Other ideas that are possible
    new LastFmPlugin(),
    new MissingMetadataFromMusicBrainzPlugin(),
    new MusixmatchSyncedLyricsPlugin(),
    new CacheEverythingOfflinePlugin(),
);

// Optionally wrap with ViewModels for MVVM.
var viewModel = new StrixDataRootViewModel(mergedLayer); // without plugins
var viewModel = new StrixDataRootViewModel(pluginLayer); // with plugins

Example: fallback images

In this example, we create a plugin that can inject a fallback image into any empty IImageCollection.

// Implement the PluginBase that corresponds to the interface you want to affect.
public class AddFallbackToImageCollectionPlugin : ImageCollectionPluginBase
{
    private readonly IImage _fallbackImage;

    public AddFallbackToImageCollectionPlugin(ModelPluginMetadata metadata, IImageCollection inner, IImage fallbackImage)
      : base(metadata, inner)
    {
        _fallbackImage = fallbackImage;
    }

    public override int TotalImageCount => base.TotalImageCount > 0 ? base.TotalImageCount : 1;

    public override async IAsyncEnumerable<IImage> GetImagesAsync(int limit, int offset, CancellationToken cancellationToken = default)
    {
        if (base.TotalImageCount == 0)
            yield return _fallbackImage;

        await foreach (var item in base.GetImagesAsync(limit, offset, cancellationToken))
            yield return item;
    }
}

// Create a container that identifies your plugin and tells us how to construct the pieces:
public class FallbackImagePlugin : SdkModelPlugin
{
    private static readonly ModelPluginMetadata _metadata = new(
        id: nameof(FallbackImagePlugin),
        displayName: "Fallback images",
        description: "When an image collection is empty, this plugin injects a fallback image.",
        new Version(0, 0, 0));

    public FallbackImagePlugin(IImage fallbackImage)
        : base(_metadata)
    {
        ImageCollection.Add(x => new StrixDataRootPlaybackHandlerPlugin(_metadata, x, fallbackImage));
    }
}

Our pledge to you

This project was built on these core values, and they serve as a promise to our community.

Free and open standard

That means community focused and no paywalls. You'll never be charged to build with or use the Strix Music standard, Strix Music SDK or the Strix Music App.

To drive our efforts, we rely on donations and contributions from users like you.

Privacy focused

No logs are generated and no servers are contacted unless you say so. Your data is exclusively put into your hands.

Perpetually preserved

Apps that work standalone and offline are a lost art.

We want to build software where a given version will always work, whether on Day 1 or Day 10,000

The entire project (docs, website, source code, build scripts, dependencies and releases) are perpetually preserved in every release, and hosted on IPFS. If anyone has these things on their local node, you'll be able to access it.

Our project will never break from a server outage, and cannot be taken down or censored.

Not even an apocalypse could ruin our hard work.


Copyright 2022 © Arlo Godfrey

strix-music's People

Contributors

amaid avatar arlodotexe avatar avid29 avatar matthew4850 avatar syndek avatar yoshiask avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

strix-music's Issues

Implement preloading in the WinUI AudioPlayerService

Background

The PlaybackHandlerService requires an implementation of IAudioPlayerService to do local playback. In the App, we have an implementation called AudioPlayerService which is built to wrap around a MediaPlayerElement.

The problem

IAudioPlayerService should support preloading track data to allow for instant playback.
This hasn't been implemented but should be possible via MediaSource.OpenAsync().
Make sure to test that this method actually works, and find a different way if not.

Cancelling core configuration in OOBE causes crash

Describe the bug

Attempting to cancel the setup of a new core during the OOBE causes the app to crash with a TaskCanceledException.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

No response

Steps to reproduce

1. Open a fresh install of Strix Music
2. In the Services tab of the OOBE, add a OneDrive core
3. Click "OK"
4. Click "Cancel"
5. Observe app crash

Visual repro steps

No response

Expected behavior

The OOBE should gracefully cancel setup of the selected core.

Additional context

No response

Help us help you

Yes, but only if others can assist.

UnauthorizedAccessException when scanning files after upgrading to Windows 11

Describe the bug

I've recently upgraded to Window 11, and after trying to load the app and test out a PR, I was met with an UnauthorizedAccessException for my personal music library, which was stored on a hard drive. Unblocking the files had no effect.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

No response

Steps to reproduce

Since I'm not sure what caused this, I'm not sure how to reproduce it on another machine. Since it's my own machine, I can fix it myself.

Visual repro steps

No response

Expected behavior

App should not crash when it encounters a file it can't read.

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item.

Zune Desktop: Collection View's Artist / song column cuts off track names.

Describe the bug

The recent feature implemented in #101 has caused a styling issue in the app. Track names get cut off at some window sizes.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

0.0.4

Steps to reproduce

1. Set up the app with any core that has tracks
2. Using the Zune Desktop shell, navigate to the Collection.
3. Resize the window and observe that the track names only take up 50% of their respective column.

Visual repro steps

image

Expected behavior

The track name should be visible.

Additional context

No response

Help us help you

No, I'm unable to contribute a solution.

StrixDataRootPlaybackHandlerPlugin should not be public

Describe the bug

All classes used internally in the PlaybackHandlerPlugin should be marked as internal, but StrixDataRootPlaybackHandlerPlugin is marked as public.

The result is that this type is visible in docs when it doesn't need to be (permalink):

image

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

No response

Steps to reproduce

N/a

Visual repro steps

No response

Expected behavior

All classes used internally in the PlaybackHandlerPlugin should be marked as internal

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item.

File based cores don't group by disc number when sorting tracks in an album

Describe the bug

When you scan an album with multiple discs to it, they appear in the UI sorted by track number, but not grouped by disc number.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

No response

Steps to reproduce

1. Setup a file based core
2. Point it to an album with multiple discs
3. When the data is output to the UI, it's sorted by track number with no regard to disc number.

Visual repro steps

image

Expected behavior

Track order should be grouped by disc number

Additional context

No response

Zune Desktop doesn't show any artists for tracks

Describe the bug

In Zune shell there is a feature where an additional artists column is shown alongside track list which actually includes all the artists involved in the making of the tracks.
If the whole collection has only 1 artist the column isn't shown at all, but if it has more than one artists on the TrackCollection an additional column is shown with all the artists.

Currently its not happening, no matter what AlbumCollection you select it doesn't display any additional artists because the AlbumCollection doesn't have more than one artist to show against a track.

Helping Info:
The shell has also subscribed the ArtistItemsCountChanged on the track but SDK doesn't notify about any new artists during scan.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

No response

Steps to reproduce

1. Launch the app.
2. Go to the collections
3. Select an album collection whom you think has should have more than one artists.

Visual repro steps

Step 1:
image

Step 2:
image

Step 3:
image

Expected behavior

The list of artists should be shown.

Additional context

No response

Help us help you

No, I'm unable to contribute a solution.

`IncompatibleSdkVersionException` thrown on startup

Describe the bug

I just cloned the repo and installed the app for the first time, but whenever I go through the OOBE and set up a new core, the core remains unloaded and the app remains on its splash screen indefinitely. Checking the logs and stepping through with a debugger reveals that the SDK version compatibility check fails on a freshly created core.

The check is performed by MergedCore's constructor:

foreach (var core in _sources)
CheckSdkVersion(core.Registration);

Somehow the core metadata's SdkVer is set to 0.0.0.0, while the current SDK version is 0.0.2.0.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

No response

Steps to reproduce

1. Install and launch app
2. Set up local files core
3. Relaunch app
4. Observe that app gets stuck on loading screen, with local files core on "Unloaded"

Visual repro steps

No response

Expected behavior

The SDK version specified in core metadata should match the version of the SDK it is dependent on.

Additional context

No response

Help us help you

Yes, but only if others can assist.

Feature: New core powered by OwlCore.Remoting

Background

OwlCore.Remoting is a lightweight RPC framework that works anywhere .NET Standard 2.0 is supported.

The framework was originally created for 2 purposes: Synchronizing UI between devices and enabling remote execution of code that is sideloaded in another application, in another process or running on another machine.

Work on this was started here, but had to be paused to focus on other parts of the SDK so we could open source on time. This includes both implementation and unit tests.

The plan

Since cores have all the members needed to know when data is changed, we can create a super clean API surface that looks roughly like this:

// Machine/Process 1- "Host" who has the running code
var core = new LocalFilesCore(folderData, config);
var host = new RemoteCore("myUniqueId", core, messageHandler);

// Machine/Process 2 - "Client" who doesn't have the running code
var client = new RemoteCore("myUniqueId", messageHandler);

// When calling a method on the client, it uses RPC calls to get the data from the host core.
var items = await client.Library.GetTracksAsync(5, 5).ToListAsync();

// The host uses RPC calls to notify the client about events and property changes.
client.Library.TotalTrackCountChanged += (s, e) => { ... };

Todo list

  • Create and write tests for all required models
  • Identify and evaluate extra services that may be needed (i.e. RemoteNotificationsService)
  • Test with a real core running out of process

FilesCore should handle removed files

Problem

Cores that are based on StrixMusic.Cores.Files do not fully handle removing music from your library when the underlying files are removed from disk.

Solution

  • Finish implementing removed items in AudioMetadataScanner, LocalFiles, FileMetadataManager, etc.
  • Test with real files (OneDrive, LocalFiles)
  • Write unit tests for FilesCore that mock the data and allow us to test that this works (may need to create a new test project)

SDK collection and item classes dependency properties needs to provide virtual property changed callbacks.

Describe the problem

Currently there is no way to handle the DP property changed events on the Collection/Item dependency property on the following essential SDK classes (there maybe more classes with similar situations):

  • TrackCollection
  • AlbumCollection
  • ArtistCollection
  • PlaylistCollection
  • TrackItem
  • AlbumItem
  • ArtistItem
  • PlaylistItem
    (Some of them might already have the fix in them)

Since DPs cannot be virtual they cannot be overridden like normal properties , to achieve this currently we are (kind of) overriding the existing DP properties using the new keyboard which hides the parent DP and is not an ideal way to do it.

image

Describe the proposed change

To fix this each base class DP should provide virtual callback of the Dependency property, in this way this callback can be overriden by shells without hiding the base class DPs using the new keyword

image

Alternatives

No response

Additional info

No response

Help us help you

No response

UWP Release mode builds fail sporadically in CI

The issue

When the CI compiles the Strix Music app in release mode, there's a high chance that release mode will fail and need to be rerun. The error message is:

Generating native code
Launching 'C:\Users\VssAdministrator.nuget\packages\runtime.win10-arm.microsoft.net.native.compiler\2.2.7-rel-27913-00\tools\ARM\ilc\Tools64\nutc_driver.exe @"D:\a\1\s\src\Platforms\StrixMusic.UWP\obj\ARM\Release\ilc\intermediate\MDIL\StrixMusic.rsp"'

##[error]C:\Users\VssAdministrator.nuget\packages\microsoft.net.native.compiler\2.2.7-rel-27913-00\tools\Microsoft.NetNative.targets(801,5): Error : Internal Compiler Error

C:\Users\VssAdministrator.nuget\packages\microsoft.net.native.compiler\2.2.7-rel-27913-00\tools\Microsoft.NetNative.targets(801,5): error : Internal Compiler Error [D:\a\1\s\src\Platforms\StrixMusic.UWP\StrixMusic.UWP.csproj]

##[error]C:\Users\VssAdministrator.nuget\packages\microsoft.net.native.compiler\2.2.7-rel-27913-00\tools\Microsoft.NetNative.targets(801,5): Error : ILT0005: 'C:\Users\VssAdministrator.nuget\packages\runtime.win10-arm.microsoft.net.native.compiler\2.2.7-rel-27913-00\tools\ARM\ilc\Tools64\nutc_driver.exe @"D:\a\1\s\src\Platforms\StrixMusic.UWP\obj\ARM\Release\ilc\intermediate\MDIL\StrixMusic.rsp"' returned exit code 1

1>C:\Users\VssAdministrator.nuget\packages\microsoft.net.native.compiler\2.2.7-rel-27913-00\tools\Microsoft.NetNative.targets(801,5): error : ILT0005: 'C:\Users\VssAdministrator.nuget\packages\runtime.win10-arm.microsoft.net.native.compiler\2.2.7-rel-27913-00\tools\ARM\ilc\Tools64\nutc_driver.exe @"D:\a\1\s\src\Platforms\StrixMusic.UWP\obj\ARM\Release\ilc\intermediate\MDIL\StrixMusic.rsp"' returned exit code 1 [D:\a\1\s\src\Platforms\StrixMusic.UWP\StrixMusic.UWP.csproj]

Done Building Project "D:\a\1\s\src\Platforms\StrixMusic.UWP\StrixMusic.UWP.csproj" (default targets) -- FAILED.

The solution

Build locally in release mode and find/fix the cause of the build error. Use Visual Studio and set the build output level to at least "Detailed" to get the most useful information.

Use loose data coupling for events on templated items in ZuneAlbumCollection

Background

In the Zune Desktop skin, we have a custom ZuneAlbumCollection that takes an IAlbumCollectionViewModel and displays them to the user.

There's also a button on each album that play the album when clicked. This button is on the ZuneAlbumItem, and when clicked, the control emits a AlbumPlaybackTriggered event.

The ZuneAlbumCollection listens for this event on each album, and when fired, plays the clicked album in the context of the IAlbumCollectionViewModel.

image

The problem

We're listening for the AlbumPlaybackTriggered event in a way that doesn't play nicely with the UI framework.

When the control is loaded, and again as needed when the Albums collection is changed, we're getting the ZuneAlbumItem control directly from the templated ItemsControl using the index, then attaching to the AlbumPlaybackTriggered event.

This results in the following issues:

  • For Albums added to the AlbumCollection after the Control is loaded, the item may not yet be finished loading in the ItemsControl, meaning we can't attach the event.

The solution

The proposed solution uses loose data coupling by wrapping each templated model in a custom ViewModel and wiring the events together.

This approach means events can be set up for every item in the ZuneAlbumCollection, and invoked by the ZuneAlbumItem only when/if the control is loaded and the button is clicked.

  • Create a custom ViewModel that wraps around AlbumViewModel
  • Give it a "RequestPlaybackCommand" we can invoke from XAML, and a "PlaybackRequested" event that's raised by the command.
  • Put it in a new ObservableCollection property. Bind to that in the UI instead of what we use now.
  • Use behaviors in XAML to invoke this new command when the "ZuneAlbumItem.AlbumPlaybackTriggered" event fires.
  • Sync the new ObservableCollection with the source, wrapping the data with our new ViewModel.
  • Listen to the event on each item in this new ViewModel and invoke playback when it's called
  • Clean up all the code from the existing approach.

Test and fixup PlaylistMetadataScanner

Background

The PlaylistMetadataScanner is used to scan files for playlist metadata, and link the tracks to known audio metadata found by AudioMetadataScanner.

This is implemented, but has some lingering TODOs, needs unit tests, and needs to be tested with real data.

What to do:

The scanner is built to work with a lot of different playlist types, and we need to make sure they all work as expected.

  • Write integration tests for each supported playlist format.
    • Mocking the playlists in unit tests would take an overkill amount of effort
    • You may add the files being tested to the repo directly. Don't include any copywrite material.
  • Make any fixes needed for bugs uncovered during testing

Inconsistent checking of file paths in playlists

Describe the bug

Most playlist formats specify a list of URIs to music streams. In most cases, these URIs will be to local files the core already has in its track repository, but some formats allow links to external resources. For example, the HLS streaming extension for M3U/M3U8 uses https:// URIs to link to segments of a streamed track on the web. This feature is essential for supporting certain streaming services, such as Soundcloud.

The initial implementation of PlaylistScanner used helper methods designed to handle this transparently and gracefully, but it appears that at some point the code was refactored incompletely. The initial design used a few helper methods in PlaylistMetadataScanner, exposed by ResolveFilePath(string path, string currentPath). The sole purpose of this method was to get a full, absolute path given the current path. This was necessary because playlists often specify paths relative to the playlist file's location, so the path would have to expanded in order for the file to be played. At some point, the TryGetHashFromExistingTracks(Uri path, IEnumerable<Models.FileMetadata?> files) helper was added, which checks the list of files for one with a matching path. This means that all external/web resources will be rejected, because the audio metadata scanner could not have found it. (The same applies to local audio files outside the directory specified by the local files core, though that is a much more complicated issue because the core does not have immediate access to the file, unlike web resources that can easily be acquired.)

The new checks also have been applied inconsistently. For example, some parser methods use only TryGetHashFromExistingTracks, others use the old ResolveFilePath, while still others use the result of ResolveFilePath and pass that to TryGetHashFromExistingTracks.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

pre-alpha

Steps to reproduce

I don't think a specific repro is useful here, but in general any `https://` URLs in a playlist will be considered invalid and ignored.

Visual repro steps

No response

Expected behavior

PlaylistMetadataScanner should be able to recognize and handle external but still accessible resources, not only local and already scanned files.

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item.

Create a proper out of box experience

Background

The SuperShell is a UI that sits on top of all other shells, and is where the user configures shells, services, and more. The SuperShell allows for configuring that app, so it was used as a temporary OOBE.

The problem

The SuperShell is not designed to provide a cohesive first-time experience for new users.

The solution

We need to design and implement a new out of box experience.

No work has been done on this yet, but we can easily lay some guidelines to follow:

  • Should match the existing app design language. See the SuperShell, documentation and homepage to get a feel for it.
  • Animations should be added for flair.
  • Should adapt to all screen sizes
  • The code for each step in the OOBE should be separated from each other. We'll need to add new steps to the OOBE later.

Remove invalid TODOs

Background

Since the project started, a number of TODO comments were left around the app.

Problem

Issues have been filed for all TODOs that are still valid, but there's a number of TODOs which are outdated or no long applicable.

Solution

Find and remove the invalid TODO comments.

Finish implementing Artists section in Zune Desktop Collection view

Background

The "Artists" section in Zune Desktop is the default view when opening the Collection tab.

The problem

Implementation of this view in Strix is incomplete.

Solution

Finish implementing this view, making sure it's faithfully matches the original app.

If you don't have the original app, the installer that we use is available here.

  • Implement default, basic collection columns (default view upon opening)
    • Artist
    • Albums
    • Songs
  • Implement sorting Artists
  • Implement sorting Albums
  • Implement multi-artist song view when album is selected
  • Implement multi-select
    • Display all albums on artist multi-select
    • Display all songs on album multi-select
  • Implement sorting of tracks (Fix tracks margins)
  • Selecting artist with one album should auto-select the album
  • Large album art when artist has single album
  • Clicking number of items should select all items
  • Clicking a group header should select all items in the group

Cores cannot be disconnected

Describe the bug

If you have multiple cores, you cannot disconnect any of them.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

0.0.2

Steps to reproduce

1. Open the strix music app
2. Set up multiple cores
3. Try to remove one by disconnecting it
4. Observe the missing disconnect button

Visual repro steps

image

Expected behavior

Should be able to disconnect

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item.

Populate more methods in SDK ViewModels return before fully completed

Describe the bug

There's a lot of async methods in the SDK's ViewModel that uses SynchronizationContext.Post(). This method only supports an Action, and does not wait for an async lambda to complete. The result is that the public SDK method's Task completes before execution is actually finished.

image

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

No response

Steps to reproduce

Run an async method such as LibraryViewModel.InitTrackCollectionAsync(). When the task completes, the tracks will not be populated.

Visual repro steps

No response

Expected behavior

The task should not complete until all code has finished running.

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item.

[Feature] File-based cores finish InitAsync() before metadata scan starts emitting data

Describe the problem

Right now, when you call InitAsync() on a file-based core (OneDrive, LocalFilesCore), the task completes before the core has a chance to scan audio files for metadata, meaning the core appears to not have any data.

Worse, even if a dev knows this, there's no reliable way for them to know when data becomes available, short of hooking into every event in the ILibrary.

Describe the proposed change

We need to fix this in a way to doesn't break the CoreModel standard. Waiting for this shouldn't require locating an extra property, or subscribing to some event, that doesn't exist on the ICore interface.

Instead, we can add a FileScanBehavior enum:

namespace StrixMusic.Cores.Files;

/// <summary>
/// The wait behavior of <see cref="IAsyncInit.InitAsync"> on a file-based <see cref="ICore"/> relative to the metadata scanner.
/// </summary>
public enum InitAsyncScannerBehavior
{
    /// <summary>
    /// <see cref="IAsyncInit.InitAsync"> will only wait for the scanner to complete if it there's no cached scan data.
    /// </summary>
    WaitIfNoData,

    /// <summary>
    /// <see cref="IAsyncInit.InitAsync"> will always wait for the scanner to complete.
    /// </summary>
    AlwaysWait,

    /// <summary>
    /// <see cref="IAsyncInit.InitAsync"> will never wait for the scanner to complete.
    /// </summary>
    NeverWait
}

This should be exposed as a property or on the constructor, with WaitIfNoData as the default value, and InitAsync on each file-based core respecting the option.

Alternatives

No response

Additional info

No response

Help us help you

No response

MergedCollectionMap refactor

Overview

The MergedCollectionMap is a class which contains all of our logic for merging collections together.
It's a very important part of the AdapterModel layer, where we merge many CoreModel instances into a single AppModel instance.

The existing code for this was created in Nov 2020, and while it mostly works, after using it for a while we've found some improvements that need to be made.

General notes

  • A "collection interface" refers to an interface such as ITrackCollection, IAlbumCollection, IImageCollection, etc.
  • Some AppModels such as ILibrary, IArtist or ITrack implement more than one collection interface.
  • Each instance of MergedCollectionMap handles a single collection interface at a time.
  • MergedCollectionMap is fully generic, allowing us to reuse the code for all collection interface implementations in AdapterModels.

Brainstorm session 4/23/2022

The follow are notes from a brainstorm session between @Arlodotexe and @amaid on how to best handle the issues that arise from merging multiple collections into one collection.

Possible approaches

Sparse collection population

  • Lazy load, but smarter
    • Use IAsyncEnumerable instead of Task<List> (implemented)
    • Use an event to notify sources changed (implemented)
  • For a mergeable item, Sources property is incomplete until we've seen all items.
  • Asking for items in the middle of the collection would require you to get all preceing items first.
  • ItemsCount is invalid until we've seen everything.

Full collection population (preload everything)

  • Preload items
    • Load/merge all contents of a collection before returning
    • Data affected by merging in new items would always be accurate
    • Significantly slower. For every collection in a collection, needs to load all/merge items before returning to get accurate merged data.
      • Example: When getting a Playlist from a PlaylistCollection, in order for Playlist.TotalTrackCount to be accurate, need to get/merge all possible tracks.
    • Renders IAsyncEnumerable pointless, everything is preloaded already
    • No need for sources changed event, sources are accurate before being returned, and would never change Required for adding/removing cores while app is running.

Hybrid (background preload)

  • Begin populating from all sources when the MergedCollectionMap is created
  • Optionally emit data as soon as it becomes available (user's choice)
  • Always emit the Count changed event
  • If data is changed on a core while scan is running, pause the scan and accommodate it (IAsyncEnumerable makes this really easy)
  • Once scan is complete, all items are merged and the item count is accurate
  • Would play REALLY nicely with a caching plugin, as the cache would be updated as the MergedCollectionMap is populated.
  • Minimizes the odds that the user will encounter problems caused by incorrect item counts, but doesn't eliminate it completely.
  • Maybe also add InitAsync to all AppModels, if you want to wait for them to be fully merged.

Questions

  • For data that is potentially wrong until we have all items merged into it, is it acceptable behavior for the data to be wrong on returned items until lazy init has finished and merged everything?
    • Yes for UI, but it's easier to write broken code (not checking the items count during iteration, getting items in an ItemsCount handler). Must be mindful of these when deciding data behavior.
    • For headless/backend only - Maybe? As long as the dev knows that the count can change when getting items.
  • What to choose?
    • The hybrid approach seems to be the best solution.

What to do

  • Clean up code codebase
  • Get sparse collection loading working as expected
  • Add in the background preloading
  • Refactor AddSource and RemoveSource on IMergedMutable to be asynchronous
  • Re-evaluate what should happen to existing data when a new source is added.
  • Implement search results and other missing types in MergedCollectionMap
  • Write unit tests.
  • Write lots of unit tests.

Remove FileMetadataManager dependency in file scanners

Background

The AudioMetadataScanner is responsible for scanning a folder for audio files, then scanning those files for audio metadata.
The PlaylistMetadataScanner is responsible for scanning a folder for playlist files, then scanning those files for playlist metadata.
The FileMetadataManager is responsible for managing the metadata scans of both audio and playlist files, then holding the data in memory and persisted to disk in "repositories".

The problem

These things have a pretty clear separation of concerns. FileMetadataManager internally uses AudioMetadataScanner and PlaylistMetadataScanner to scan files.

However, the scanners also take on a dependency of FileMetadataManager. It appears that this is currently being used to get scan type settings and update the number of files or folders scanned.

These are trivial things that don't require a full dependency on FileMetadataManager. Instead, let's:

  • Pass ScanType settings into scanner constructors
  • Use an event on the scanners to notify of a change in file found or files scanned
  • Write unit tests to ensure it works, if missing.

Empty lines in M3U playlists cause the playlist parser to fail

Describe the bug

M3U playlists containing empty lines throw an IndexOutOfRangeException at the following line when attempting to parse.

This should be a simple fix, there just needs to be a case for skipping the current line if it has zero length or is empty/whitespace.

I suspect there's a larger issue with error handling, as currently the problematic playlist causes the playlist scanner to halt as well, even if there are other valid playlists to scan.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

No response

Steps to reproduce

1. Create an M3U playlist and add an empty line anywhere in the file
2. Attempt to load said playlist into Strix Music
3. Observe an `IndexOutOfRangeException` in the log output, and the playlist scanner halting

Visual repro steps

No response

Expected behavior

Playlist parses ignores empty lines.

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item.

Fix freezing when disposing / unloading a core

Background

To understand this issue, you'll need an understanding of how we structure data in the SDK.

By default, the data from databases, APIs (like Spotify or Deezer), file systems, and backends in general, are always:

  • Modular and reusable anywhere.
  • Object-oriented (models hold data and other models)
  • Easily scoped to a context (login credentials, API keys, etc)

These properties are intrinsic of every object-oriented data structure, spanning from JSON to SQL to XML to C# objects.
Far too often, one or more of these things are lost due to limitations of the architecture built to interact with it.

In order to maintain these things throughout the entire SDK and all apps built on it, we had to conceptualize a new architectural pattern:

AppModels

  • These transform your backend's data structure into normal C# objects.
  • Each model has properties for data, methods that returns other C# objects, or events that notify of changes, if needed.
  • Since objects always return objects, it maintains the same scope, structure and reusability of your data.
  • As normal C# objects, this has the bonus of making your application data completely portable.
    • Models are always constructed by other models, so you only need to configure dependencies at the root object.
    • Dependencies are transparently trickled down to other new objects before being returned or emitted by an event.
    • The result is an object tree of your app's data, where objects are easy to get and ready to use 🚀

This concept can be applied to any application which is powered by a structured graph of data.

AppModels in Strix

In the Strix SDK, our AppModels are much more complicated than this, as we're doing some complicated things. Our model layers include:

  • CoreModels - interfaces implemented by service sources.
    • Includes OneDriveCore, LocalFilesCore, RemoteCore, etc.
  • AppModels - interfaces which represent merged CoreModels, and has extras designed for building an application.
    • AdapterModels - an implementation of AppModels that adapts or merges one or more CoreModels into an AppModel.
    • ViewModels - an implementation of AppModels that provides INPC and ObservableCollections for the MVVM pattern.
    • PluginModels - an implementation of AppModels that uses model plugins to alter behavior of models as cleanly as possible.

The PluginModels and ViewModels wrap around implementations of AdapterModels
The AdapterModels wrap around one or more implementations of CoreModels.
The CoreModels wrap whatever code is needed to interact with the music source.

Overview and history of model plugins

When we created the model plugins, we wrote a ton of unit tests. I mean an overkill number of tests, in the unit of thousands of tests after all combinations were run.

The testing of all the different possible combinations for plugin models was done by using enum flags, allowing us to feed every combination to the same unit test method.

We made sure the correct underlying plugin was accessed using a bit of reflection to get the relevant properties, throwing an AccessedException{T}, catching it and making sure the T (which is always the containing class) was what we expected.

The problem

⚠️Make sure you've read the above.⚠️

An understanding of our data layers and model plugins is required to solve this issue.

At some point after we implemented model plugins in the app, the DisposeAsync() method began to hang when a model plugin was between us and the AdapterModels.

When we tried to "disconnect" the core from the UI, it was unregistered, removed as a source where needed in the AdapterModels layer, and the DisposeAsync() method was called to clean up unmanaged resources used by the core (such as open network connections)

After brief debugging, I found that it was still calling the underlying methods, making it all the way to the relevant cores, but the task still just kinda hangs there.

We had trouble consistently reproducing it, but we modified the DisposeAsync() logic slightly and added more unit tests. But though it briefly worked and the tests passed, the issue wasn't actually fixed.

Here's what needs to be done:

  • Find consistent repro steps for the hanging when unloading a core.
  • Write unit tests for the repro, making sure the tests fail.
  • Fix the issue and make sure the tests pass.
  • (optional) Get yourself a cape or something, because you're our hero.

InvalidCastException when loading Zune shell with debugger attached

Description

An InvalidCastException is thrown when loading the Zune Desktop shell with the debugger attached.

Background

This issue occurs on startup. To the user, there is no regression as a result of the exception, and it does not take down the app when thrown.

Repro

Steps to repro

  1. Open the solution in Visual Studio
  2. Make sure InvalidCastException is enabled in the Exception settings
  3. Build and deploy the app
  4. Observe the error:
    image

Create build scripts

Background

In our Pledges, we promised:

The entire project (docs, website, build process, dependencies, SDK, app, etc) are perpetually preserved in every released binary and on our website, all hosted on IPFS. If anyone has these things, you'll be able to access it over IPFS

Problem

In order to do this, we need to create build scripts that can perform all of our CI standalone, and put them in the repo.

Solution

Create powershell scripts that can

  • Apply git tags for a release
  • Generates a changelog based on tags
  • Generate the nuget package
  • Generate documentation website
  • Generate app releases (Wasm, Uwp)
  • Generate strixmusic.com website
  • Zip up the entire git repo
  • Bundle it all together in a nice folder structure
  • Include all these things as part of the git repo

[PlaybackHandlerService] PlayFromNext/Previous does not properly shift internal queue

Background

The PlaybackHandlerService is a class which

  • Handles collection playback
  • Has an internal track queue (PreviousItems, CurrentItem, NextItems)
  • Handles shuffling, unshuffling and repeat states
  • Uses a provided IAudioPlayerService for playback, or the source core for remote playback.
  • Exposes a StrixDevice, an IDevice which turns the playback handler into a useable SDK device.

The problem

When implementing this, most of our testing was done against the PreviousAsync and NextAsync methods. The PlayFromPrevious and PlayFromNext methods were largely neglected.

In both methods, we need to

  • Implement shifting the internal queue
  • Make sure repeat states are handled appropriately
  • Ensure events are fired as expected (CurrentItemChanged, NextItemsChanged, etc)
  • Write unit tests to make sure these methods stay working as expected.

Docs lack any charts, graphics or visual aids

Background

The Strix Music SDK, while well architectured, is no simple thing. We have several explainers for the most essential things devs should know about in our documentation.

The problem

There's plenty of code and screenshots in the docs, but no visual aids to help simplify things.
A couple of charts helping explain some of the hard parts would go a long way for new devs who are just onboarding and getting a feel for things.

The solution

In the docs, particularly in places where it gets complicated - paragraphs of text, sets of bullet points - we should create a graph explaining the relations between all the things being discussed.

The How it works section is a good example of this.
The Home page is another that could make great use of this.

Ideally, charts should be exported as SVGs and stored somewhere we can edit it, if needed.

LucidChart should be able to do one or both of these

Test and fixup merged search

Background

The MergedSearch, MergedSearchResults, MergedSearchQuery, and MergedSearchHistory classes are all of our Search components in the AdapterModels layer.

These models are implemented, but have some lingering TODOs and needs both unit tests and to be tested with real data from multiple cores.

What to do:

  • Write unit tests for all of these models to make sure they are fully implemented and work as expected.
  • Make any fixes needed for bugs uncovered during testing
  • Once we have search results coming from at least 2 cores, test with real data.
  • TODO: Merge search results based on query

Add AppModel changed events to facilitate adding a source with a non-null value

Background

CoreModels vs AppModels

CoreModels and AppModels were split into 2 clearly distinct API surfaces early in development. Though the data structure is nearly identical, AppModels have some inherent differences that required them to be separate from CoreModels.

The reasons for this boil down to:

  • Feature responsibility, such as download info in AppModels, but not in CoreModels. It's the responsibility of the application to do downloads, and CoreModels are just the data providers.
  • To have an API surface that can be clear the data came from many sources.
  • To adjust Core or App model interfaces, without affecting all implementations everywhere. This is what makes this change possible.

Nullable properties

In AppModels, interfaces like IAlbum, IArtist, IPlaylist, ISearch, and ITrack all contain some property that is nullable, such as SearchHistory or RelatedItems.

This matches what's found in the CoreModels. Cores either do or do not supply this information, so there is no need for events to know if these properties change in CoreModels.

The problem

This data may not change in a core, but since AppModels are merged, the data can change there if you add a new source with a non-null value.

We don't have any events in the AppModels that help us propagate changes for these nullable properties. We need to add them to the interfaces and all implementations (AdapterModels, PluginModels, ViewModels)

Metadata scanners should be using IAsyncEnumerable instead of Task{IEnumerable{T}}

Background

In large part, the metadata scanners in the namespace StrixMusic.Sdk.FileMetadata.Scanners were created by many devs collectively, with loose coordination from @Arlodotexe.

Because of the way we developed this, we ran into a few unexpected problems in our first pass, namely that events were firing so quickly and so often that it overloaded the GC and caused the UI to be unresponsive when the debugger was attached, even when running in a background thread (all threads pause before GC can do cleanup).

We solve this by batching results on an event handler, then returning all the scanned metadata as an IEnumerable.

The problem

This API surface can be dramatically improved. Instead of using a Task that contains an IEnumerable of all the metadata, we should be using IAsyncEnumerable and returning the metadata as it's scanned.

This applies to both PlaylistMetadataScanner and AudioMetadataScanner.

We'll also need to evaluate what this means for file-based cores, and if it can be applied throughout the codebase so that each song appears in the UI as it's scanned, without overloading the GC.

Dynamic back navigation in Zune Shell

Background

In the original Zune app that the Zune Desktop skin was based on (version 4.8), pressing the back button would navigate you back to the most recent top-level component (Quickplay, Collection & collection tabs, Settings & settings tabs).

The problem

The Zune Desktop skin only closes the settings view and does not do any dynamic back navigation

The solution

Implement dynamic back navigation on the Zune Desktop skin to match the behavior of the original app.

If you don't have the original app, the installer that we use is available here.

Add debug boot mode

Background

Right now, when the app is starting up, we display "Quips". These are witty remarks that change depending on language, time of day or year, region, etc., and are meant to entertain the user briefly while the app loads.

Problem

The startup process in the app is a rather complicated one. It would be useful, or at least more entertaining to our advanced users, to have a debug mode which displays log messages while starting up.

Solution

  • Add a "debug" mode to the advanced app settings
  • In AppLoadingView.xaml, display output from the logger either alongside or instead of quips.

Designs can be proposed and discussed below or in the PR, if needed 👇

Improved source selection for properties in merged models

Background

The AdapterModel layer is where we merge many CoreModel instances into a single AppModel instance.

There are 2 major parts to this:

  • The MergedCollectionMap, where we join collections together.
  • The PreferredSource, a concept used to select which source to use for single properties.

Right now, we're simply selecting the first source in the CoreRanking. This ranking is used to decide the order we pull items from for sources in merged collections.

However, we've also been using it to assume that the first source is the preferred source, and we've been using that for property values in models that have been merged.

We can do better.

Possible approaches

There's a few ways we could improve this:

  • Option 1: Outright provide the core to use for properties, instead of assuming
  • Option 2: Make sure all properties match in order to merge
    • This would make source selection irrelevant, because properties would always match
    • Properties could change, so we can't guarantee they always match unless we remove items that no long match.

What to do

For now, let's use option 1. Option 2 will need separate issue and discussion to fully flesh out, as it could bleed into the conversation about customizing how sources are merged.

  • MergedCollectionConfig should contain:
    • A property of type ICore for the preferred source.
    • An event for when the preferred source changes
  • In Merged models
    • Use this property for the preferred source
    • Listen to the event for when the preferred source changes
    • Update all dependent properties when the preferred source changes
    • Detach and re-attach relevant events when the preferred source changes
  • Make sure Guards are in place to:
    • Ensure a new preferred source is present in the list of available sources
    • Ensure that a removed source is not the current preferred source.
  • Unit tests should be present for all above changes.
    • These don't exist yet, so you'll need to create them.
    • There will be a lot as you'll need to test each merged model, so be clever about maximizing code reuse.

Handle live filesystem changes in StorageCore

Problem

Cores that are based on StrixMusic.Cores.Storage do not fully handle when files are added / removed while the app is running.

Solution

  • Close off #102 first
  • Subscribe to the file watcher for crawled folders in FileMetadataManager and handle the changes appropriately
  • Test the changes firsthand with a real core
  • Write unit tests to ensure the changes stay working

Refactor Zune SettingsView to remove ViewModel

Overview

The Zune settings created very early in development, before we had a good handle on architecture. We've learned the hard way to just put View code in the control code-behind, instead of trying to put View code in a ViewModel.

What to do

In StrixMusic.Shells.ZuneDesktop.Controls.Views.Settings, the SettingsView is doing a couple of strange things that need to be cleaned up or removed:

  • Remove the dedicated ZuneDesktopSettingsViewModel and move code into the SettingsView control.
  • Use the ZuneDesktopSettings directly. This uses OwlCore's SettingsBase, which implements INPC for settings already, so we can just bind to the properties.
  • Remove the call to SaveAsync in property setters.
  • In the SaveClicked event handler, call SaveAsync() on ZuneDesktopSettings
  • In the CancelClicked event handler, call the LoadAsync() method on ZuneDesktopSettings to reload the persisted values into memory and overwrite unsaved changes.

Nullable CacheFolder property shouldn't be required to use AudioMetadataScanner

Background

The AudioMetadataScanner performs image processing as part of its scan, taking care of linking and re-emitting image IDs to known metadata.

The problem

When processing is complete, image files are stored in an AbstractStorage folder, the CacheFolder property. When using the scanner, it will crash if you don't set this property, and we don't provide a way to set it without using a FileMetadataManager.

The solution

  • Change the implementation so image processing simply doesn't happen if there's no folder to save it to.
  • Rename the property to ImageOutputFolder.
  • Create a new constructor that allows the user to set the property.

SDK images should use Stream instead of Uri

Background

In the Strix Music SDK, all image (interfaces inheriting from IImageBase) uses a Uri to provide images.

The problem

Unlike most of the data in the SDK, images are not raw data. Similar to an audio stream, it is a resource.

As part of the standard, with the exception of things that self-classify as an external resource (UriCollection), all data should be provided directly from the cores as data, instead of using Uris to point to the resource elsewhere.

There's a number of reasons for not using Uris:

  • Allows plugins to interact with the data
    • Resource optimization
    • Local caching
  • Uris might be behind a nonstandard protocol or inaccessible address, which would block access to that data.
  • Streams allow you to open a direct stream to the data, meaning
    • We can move all image resize processing from the file scanner to a plugin.
    • It works even if that data is fabricated in memory and wouldn't have been accessible from a Uri.

The solution

Change IImageBase to use Stream instead of Uri.

  • Remove the Uri property
  • Add OpenStreamAsync method that returns a Task<Stream>
  • Ensure all of the following get the new implementation (recommend using ReSharper's "Apply Refactoring" here)
    • BaseModels (start here)
    • CoreModels
    • AppModels
    • AdapterModels
    • PluginModels
    • ViewModels
    • Model plugins
    • Mock models
    • Unit tests

AudioMetadataScanner is treating song artists as album artists

Background

Audio files usually have 2 different kinds of artists:

  • Album Artist, or the person/group that the containing album was released under
  • Song Artist, otherwise known as "Performers". This gives credit to artists to helped work on an individual song.

The problem

In #106, we added support for returning multiple artists from a scanned files, but we didn't properly distinguish between Album Artist and Performer. Instead, they're mashed together and all listed as Album artists.

Affected area

  • Strix Music SDK
  • Strix Music App

Regression

0.0.2-app-alpha

Steps to reproduce

1. Set up a files core (either local files or OneDrive, anything that uses AudioMetadataScanner)
2. Make sure to add audio files where songs have different song artists than the album artist
3. Open the app and let it scan the files
4. Observe the bug as the artist list loads. Performers show as album artists.

Visual repro steps

Visually, the bug manifests as this:
image

Expected behavior

Song artists should only exist on tracks, not as individual artists who have released entire albums.

Additional context

No response

Allow users to adjust core ranking

Background

The AdapterModel layer is where we merge many CoreModel instances into a single AppModel instance.

To decide the order of sources that we pull data from for merged collections, we use the CoreRanking, which is an ordered list of the instance IDs for all possible sources.

The first item in the list is the most preferred, the last item in the least.

The problem

The ranking is non-configurable. It's decided by the order which a core was registered - with no way for the user to rearrange it.

The solution

We need to:

  • In the "Services" tab of the SuperShell, add the ability to rearrange the cores, and save the order as the CoreRanking.
  • Move all code related to initializing and reordering the CoreRanking into CoreManagementService.
    • Move InitializeCoreRankingAsync out of AppLoadingView.xaml.cs.
    • Value should still be persisted with AppSettings.

ModelPlugin tests failing on some machines

Background

When we created the model plugins, we wrote a ton of unit tests. I mean an overkill number of tests, in the unit of thousands of tests after all combinations were run.

The testing of all the different possible combinations for plugin models was done by using enum flags, allowing us to feed every combination to the same unit test method.

We made sure the correct underlying plugin was accessed using a bit of reflection to get the relevant properties, throwing an AccessedException{T}, catching it and making sure the T (which is always the containing class) was what we expected.

The problem

I created the model plugins on a single machine, running Windows 10 21H2 (Build 19044) and Visual Studio 2022 17.1.6.
For some reason, a select number of these tests simply fail when using other machines.

The reason why isn't clear, but it's something we need to fix

Adjust seasonable quips for southern hemisphere

Background

Right now, when the app is starting up, we display "Quips". These are witty remarks that change depending on language, time of day or year, region, etc., and are meant to entertain the user briefly while the app loads.

Problem

Seasonable quips are based on the current date, but these are based on the dates seasons stop and start for the northern hemisphere. In the sourthern hemisphere, the dates that seasons start and stop are different, meaning anyone using the app in the southern hemisphere will see quips for the wrong season.

Solution

  • Find a way to detect if the user is in the northern or southern hemisphere (without asking for geolocation or making network calls)
  • Adjust date ranges for season quips for both north and south hemisphere.

Implement PlaybackQueue in StrixDevice

Background

The StrixDevice is an IDevice which turns the information in a playback handler into a useable SDK device. This is largely used for controlling local audio playback.

There are 2 types of queues in the SDK:

  • The PlaybackHandler queue, which is comprised of 3 separate properties (PreviousItems, CurrentItem, NextItems)
  • The playback queue on IDevice, which is a standard ITrackCollection.

The problem

While StrixDevice is mostly implemented, the PlaybackQueue isn't. That means the user can neither see the actual queue nor modify it when playback is local.

We need to:

  • Create an implementation of ITrackCollection, call it something like StrixPlaybackQueueCollection.
  • When an item is added to this collection, queue it with the underlying PlaybackHandlerService.
  • When an item is removed from this collection, remove it from the underling queue.
  • Ensure the Sources property contains references to cores for all items in the collection.
    • Emit the SourceChanged event when this list is changed (as needed)
  • Write unit tests to ensure this new PlaybackQueue stays working as intended.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.