Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Offline Storage] Some aborted/interrupted downloads continue to download #6311

Open
loicraux opened this issue Feb 28, 2024 · 2 comments
Open
Labels
component: offline The issue involves the offline storage system of Shaka Player priority: P2 Smaller impact or easy workaround type: bug Something isn't working correctly
Milestone

Comments

@loicraux
Copy link
Contributor

loicraux commented Feb 28, 2024

Have you read the FAQ and checked for duplicate open issues?
Yes

If the problem is related to FairPlay, have you read the tutorial?
N/A

What version of Shaka Player are you using?
Latest version 4.7.11

Can you reproduce the issue with our latest release version?
Yes

Can you reproduce the issue with the latest code from main?
Yes

Are you using the demo app or your own custom app?
Own custom app.

If custom app, can you reproduce the issue using our demo app?
This cannot be reproduced in Shaka Player demo app, since there is no mean to interrupt/abort a download operation from the Demo UI. (correct me if I am wrong)

What browser and OS are you using?
Chrome in a VMP-signed Electron.

  • electronVersion : 28.1.3
  • chromeVersion : 120.0.6099.199
  • shakaPlayerVersion : 4.7.11
  • widevineCDMVersion : 4.10.2710.0
  • operatingSystem : Linux x64 6.5.0-21-generic

For embedded devices (smart TVs, etc.), what model and firmware version are you using?
N/A

What are the manifest and license server URIs?
This is not specific to a manifest nor a specific license server.

What configuration are you using? What is the output of player.getConfiguration()?
See code sample below.

What did you do?
My app basically revolves around downloading some content, but also being able to cancel an ongoing download. It is important that when a download is interrupted, the fetching of segments operations are indeed stopped and the manifest-v5 table does not contain the interrupted download anymore.

Here is the code that runs when the download button is clicked :

async function handleClickOnDownloadButton(): Promise<void> {
    try {
        const download = await startDownload(videoToDownload); // See function below
        // Once the download is complete, we add it to the offline store :
        addCompletedDownload(download);
    } catch (error: unknown) {
        if (error instanceof shaka.util.Error) {
            const { severity, category, code } = error;
            if (code === shaka.util.Error.Code.OPERATION_ABORTED) {
                logInfo('Download operation cancelled by the user');
                // Here I know that the cancellation has been successful,
                // hence removing the download in progress from UI :
                removeCanceledDownloadInProgress(videoId);
                return;
            }

            if (code === shaka.util.Error.Code.STORAGE_LIMIT_REACHED) {
                logError('Storage limit reached while starting download', error);
                return;
            }

            logError(`Shaka error while starting download. Severity: ${severity}, category: ${category}, code: ${code}`, error);
            return;
        }

        if (error instanceof Error) {
            logError(`Error while starting download. Error message is: ${error.message}`, error);
            return;
        }

        logError('Unknown error while starting download', error);
    }
}

async function startDownload(video: Video): Promise<shaka.extern.StoredContent> {
    // CONFIGURE THE PLAYER
    player.configure({
        drm: {
            servers: {
                [GOOGLE_WIDEVINE_KEY_SYSTEM]: getLicenseServerUrl(video)
            },
            logLicenseExchange: true,
            preferredKeySystems: [GOOGLE_WIDEVINE_KEY_SYSTEM],
            persistentSessionOnlinePlayback: true,
            advanced: {
                [GOOGLE_WIDEVINE_KEY_SYSTEM]: {
                    videoRobustness: 'SW_SECURE_CRYPTO',
                    audioRobustness: 'SW_SECURE_CRYPTO',
                    persistentStateRequired: true
                }
            }
        },
        offline: {
            usePersistentLicense: true,
            progressCallback
            trackSelectionCallback
        }
    });

    // START THE DOWNLOAD
    const abortableOperation = storage.store(getManifestFileURI(video), someMetadata);

    // UPDATE THE STORE STATE
    dispatch(
        createAddDownloadInProgressAction({
            video: someMetadata,
            progress: 0,
            abortableOperation
        })
    );

    // RETURN THE PROMISE
    return abortableOperation.promise as Promise<shaka.extern.StoredContent>;
}

The code that basically runs when the user clicks on the CANCEL button to cancel an ongoing download is the following :

async function cancelDownload(videoId: string): Promise<void> {
    assertIsDefined(storage, 'Storage is not defined !?');
    const downloadInProgress = downloadsInProgress.get(videoId);
    assertIsDefined(downloadInProgress, `Download with id=${videoId} is not in progress !?`);
    const { abortableOperation } = downloadInProgress;
    await abortableOperation.abort();
}

What did you expect to happen?
I expect :

  1. the downloads of segments to stop
  2. the corresponding manifest to be removed from manifest-v5 table of shaka_offline_db database in IndexedDB

(I would be OK with a few extra progress events sent and even a couple of orphaned segments in IndexedDB after the download has been interrupted/canceled)

What actually happened?
Sometimes, but not always (I'd say one out of two times), when I click on the button to cancel the download in progress, the download actually continues, right to the end (100%). At the end, the manifest-v5 table contains the download I had interrupted!

What I've observed is that when the download interruption is successful, a shaka.util.Error.Code.OPERATION_ABORTED exception is thrown by the call to store(). I can then catch this error in my code and make the current download disappear from my UI. For me, this exception is indeed a sign that the download cancellation has been successful.

But sometimes (I'd say one out of two times), this error isn't thrown by store() following a call to abort(), so the download continues, as if I hadn't called abort().

At this point, the user needs to click a couple of times on the cancel() button until the cancellation eventually occurs...

At first I thought that this problem of the interrupt not working only appear if you interrupt the download very quickly at the beginning, but in fact it can occur at any time during the download.

Note #1 : I have also observed that a couple of progress events are sent to the progressCallback, even if the download has already been interrupted (meaning after I have caught the shaka.util.Error.Code.OPERATION_ABORTED exception), but this is not a problem to me, I can easily ignored those events.

Note #2 : I have also observed that after a download is successfully interrupted, there are most of time always a couple of orphaned segments left in the segment-v5 table. I think this another issue to address, not related to the interruption of the download.

@loicraux loicraux added the type: bug Something isn't working correctly label Feb 28, 2024
@shaka-bot shaka-bot added this to the v5.0 milestone Feb 28, 2024
@joeyparrish joeyparrish added priority: P2 Smaller impact or easy workaround component: offline The issue involves the offline storage system of Shaka Player labels Mar 12, 2024
@loicraux
Copy link
Contributor Author

For those facing this issue, there is a workaround to mitigate it until this bug is fixed :

The idea is to have a sort of store in your application where you have your downloads in progress. On the download in progress data structure you need at least 2 properties :

  1. A boolean flag to tell whether or not this download in progress is being cancelled
  2. A reference to the abortableOperation that was returned by storage.store when you started the download...

When in the UI, the user cancel a download in progress, you need to :

  1. Abort the abortableOperation : abortableOperation.abort()
  2. And mark the download in progress has being cancelled (set the boolean flag mentioned above to true)

.. But you do not remove the download in progress from your store at this point !

Now it can happen that the call to abortableOperation.abort() does not work (this is the bug filed here).

The idea/workaround is to have your progressCallback event handler checking whether or not the content being downloaded is indeed already cancelled (by checking the flag)... Is this is the case, then you must attempt once again to abort the abortableOperation (abortableOperation.abort()) and do not update your store with progress data...

After a certain number of abortion attempts, the download will eventually successfully be aborted. Shaka player code will in this case throw an error with code shaka.util.Error.Code.OPERATION_ABORTED and it's up to you catch this error (which is the sign that the abortion is successful) and now remove the download in progress from your store.

This workaround works fine, but it can also happen that the user cancels the download when it's almost finished, in this case it can happen that none of your abortion attempts work and the download --despite being cancelled-- ends up completed in IndexedDB. So you have to add an extra workaround in your code that is run after the awaited download operation, to check whether or not the completed download corresponds to a download in progress marked as being cancelled in your store ... If this is the case, then you have to storage.remove the completed download (and also in any case remove the download in progress from your store)...

There you go, it may help some people who are facing this problem...

@avelad
Copy link
Collaborator

avelad commented Apr 22, 2024

@loicraux are you interested on send a PR to fix it? Thanks!

@avelad avelad modified the milestones: v4.8, v4.9 Apr 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: offline The issue involves the offline storage system of Shaka Player priority: P2 Smaller impact or easy workaround type: bug Something isn't working correctly
Projects
None yet
Development

No branches or pull requests

4 participants