Skip to content

Restore reading position

Introduction

This tutorial will teach you how to restore a previous reading position when a user reopens a publication.

Prerequisites: Basic navigation tutorial

Full code example: Restore reading position tutorial repo

Store

When the reader position changes, by for example a user changing page, an engine event is emitted. Create a class, in a new file AppReadingPositionRestorer.ts, that listens for the readingPositionChanged event and from that get the current reading position. The event listener should be added to the ReaderView

ts
export class AppReadingPositionRestorer {
    constructor(
        private readerView: IReaderView,
        private publicationSignature: string,
    ) {
        readerView.addEngineEventListener('readingPositionChanged', this.onReadingPositionChanged);
    }
}

Then add a function that stores the position in local storage. Use the publication signature as the key in the local storage to easily retrieve the saved position when the same publication is loaded again.

ts
private onReadingPositionChanged = (event: IReaderViewEngineEvent): void => {
    // Store reading position every time it changes. The position can be null if the reader view
    // is not rendering any publication content.
    const position: IContentLocation | null = event.readerView.getReadingPosition();
    this.store(this.publicationSignature, position);
};

// Store the reading position in local storage using the publication signature as key
private store = (publicationSignature: string, readingPosition: IContentLocation | null): void => {
    if (readingPosition) {
        try {
            window.localStorage.setItem(publicationSignature, readingPosition.getLocator().toString());
        } catch (e) {
            Logger.logError("Failed to store the reading position", e);
        }
    }
}

The reading position from the event will be a ContentLocation, but to restore the location you only need the Locator. The Locator can be serialized to a URL that contains the publication signature and selectors pointing to content in the publication.

Restore

To restore the previous reading position, read the saved Locator from local storage, and go to that position using ReaderView.goTo(). If there is no saved Locator or if an error occurs, default to goToStart() instead.

ts
restore = (): void => {
    try {
        const serializedLocator = window.localStorage.getItem(this.publicationSignature);
        if (serializedLocator) {
            this.readerView.goTo(serializedLocator).catch(reason => {
                Logger.logError("Failed to navigate to stored position", reason);
                this.goToStart();
            });
        } else {
            this.goToStart();
        }
    } catch (e) {
        Logger.logError("Failed to read from local storage", e);
        this.goToStart();
    }
};
private goToStart = (): void => {
    this.readerView.goToStart().catch(reason => {
        Logger.logError("Failed to navigate to start of publication", reason);
    });
};

Wrap up

To use your new reading position restorer, in the App, create a new AppReadingPositionRestorer and add a call to restore() just after the publication is loaded. This way you both keep track of any new positions to save and restore, if there was any saved in the local storage, on publication load.

Change the loadEpub method in App.ts to include the calls to the reading position restorer.

ts
async loadEpub(epubFile: Blob): Promise<void> {
    const ocfResourceProvider = await EpubOcfResourceProvider.createFromBlob(epubFile);
    const epubPublication = ocfResourceProvider.getDefaultPublication();
    if (epubPublication) {
        const licenseOptions = {
            userToken: await SimpleObfuscation.obfuscate(this.userId),
            publicationToken: await SimpleObfuscation.obfuscate(epubPublication.getHashSignature())
        };
        if (this.readerPublication) {
            this.readerView.setReaderDocuments([]);
            await this.readingSystemEngine.unloadPublication(this.readerPublication);
        }
        this.readerPublication = await this.readingSystemEngine.loadPublication(epubPublication, undefined, licenseOptions);
        this.readerView.setReaderDocuments(this.readerPublication.getSpine());
        if (this.restorer){
            this.restorer.destroy();
        }
        this.restorer = new AppReadingPositionRestorer(this.readerView, epubPublication.getHashSignature());
        this.restorer.restore();
    } else {
        return Promise.reject('No publication found in this EPUB file.');
    }
}

Add the destroy method to AppReadingPositionRestorer to remove the old event listener when a new publication is loaded.

ts
destroy = (): void => {
    this.readerView.removeEngineEventListener('readingPositionChanged', this.onReadingPositionChanged);
};

Don't forget to remove the goToStart() from index.ts since the initial goTo will now be managed by the AppReadingPositionRestorer.

Definitions

TermDescription
ContentLocationA location in the publication with references to related information such as navigation items or reader documents. Can be a range or a single point.
Local storageA storage in the web browser. Data stored are saved after the browser closes.
LocatorUsed to reference locations, both single point and ranges, in a publication. Can be serialized to format specific to the type of publication.
PublicationSignatureAn unique hash signature that can be used to identify a publication.
ReaderViewHandles rendering and navigation.
ReadingPositionThe position in the publication where the reader currently are reading.
SelectorUsed to select partial content within a resource in the publication