Skip to content

Conversation

@HadrienGardeur
Copy link
Member

@HadrienGardeur HadrienGardeur commented Jan 28, 2026

Goal

Synchronizing progression using OPDS has been by far the most requested features over the last few years.

Wth this PR the goal is to provide a standardized solution that could work across many types of publications.

The document itself is fairly barebones and will be fleshed out once there's a general agreement on what it contains. The current approach is based on the minimum information required to make this work.

Links

Syntax

Looking at the required property, this currently means:

  • a timestamp (modified)
  • a way to identify the device that submitted the last-known progression (device)
  • and a progression in the publication (progression)

The timestamp is self-explanatory.

For the device, I went for two required values:

  • an id meant to uniquely identify a device (useful for clients/servers potentially) based on a URI
  • and a name meant to be displayed to the user using a string

If I finished reading chapter 1 on device A, next time that my progression is synchronized on device B, it should tell the user that the progression has changed and mention that this information is coming from device A. That's what this information is for.

A client could also recognize itself using the id and skip all of this.

For the progression itself, a total progression in the publication in percentage (number between 0 and 1 in JSON) feels like the most universal thing.

How we calculate progression is a different story, one that could be handled through best practices as we flesh out the document further.

In addition, I've also added support for references which open the door to media-specific fragments suche as page for PDF. A list of well-known fragments has also been included.

I initially started working on this based on Readium Locators, but kept simplifying things until progression (totalProgression in Readium Locators) and references were the only two things left.

Examples

Example 3: Progression in an EPUB

{
  "modified": "2026-01-27T11:00:00Z",
  "device": {
    "id": "urn:uuid:019c0047-cc8d-7ec4-a3c3-938ccadc020a",
    "name": "Ebook Reader (Pixel 10 Pro)"
  },
  "progression": 0.0174920
}

Example 4: Progression in an EPUB with Media Overlays

{
  "modified": "2026-01-28T00:24:00Z",
  "device": {
    "id": "urn:uuid:019c0047-cc8d-7ec4-a3c3-938ccadc020a",
    "name": "Ebook Reader (Pixel 10 Pro)"
  },
  "progression": 0.0174920,
  "references": ["#t=40.274", "chapter1.html#par36"]
}

Example 5: Progression in a PDF using a Web Reader

{
  "modified": "2026-01-28T19:00:00Z",
  "device": {
    "id": "https://reader.example.com",
    "name": "Web Reader"
  },
  "progression": 0.048204
}

Example 6: Progression in an audiobook

{
  "modified": "2025-12-25T12:00:00Z",
  "device": {
    "id": "urn:uuid:019c0049-6e8c-745c-adb1-5b03f8ad50c4",
    "name": "Audiobook Player (Sonos Era 300)"
  },
  "progression": 0.72370325,
  "references": ["#t=849.250"]
}

@mickael-menu
Copy link
Member

In the current examples, a fragment like #t=849.250 is ambiguous for publications with multiple resources:

  • Is this 849.250 seconds from the start of the entire publication (linearized across all resources)?
  • Or is it relative to a specific resource in the reading order?

Suggestion: We could disambiguate by allowing fragments to optionally include the resource HREF as a prefix:

  • Global/linearized position (relative to the whole publication): #t=849.250
  • Resource-specific position: chapter3.mp3#t=32.250

The resource HREF would match exactly what appears in the publication's reading order and must be a valid URL.

Both forms could coexist in the same fragments array:

"fragments": ["#t=849.250", "chapter3.mp3#t=32.250"]

Additional considerations:

  1. Just the HREF (no fragment): Should chapter3.mp3 (without #...) be valid to denote the start of a resource? This could be useful when the position is at the beginning of a specific chapter, or for comic books / FXL. But then we're back to Locator's href. ;) Perhaps this should be named locations instead of fragments. It should contain only valid URLs, including those with fragments, similar to window.location in JavaScript.
  2. Multiple HREFs: If we embed HREFs in fragments, what happens with conflicting entries like ["chapter3.mp3#t=32", "chapter4.mp3#t=15"]?

@HadrienGardeur
Copy link
Member Author

Thank you for your comment @mickael-menu, I think that this is one of the key discussion point for this draft PR.

First of all, it's important to point out that we want to cover a wide range of publication formats:

  • some of them are standalone (PDF, M4B and many audio/video formats)
  • some of them have a container with multiple resources in them (EPUB, CBZ, Zipped audiobooks)
  • and we also have Web Publications where there is no container but a bunch of resources available on the Web

For standalone formats, using a fragment on its own makes perfect sense and we need to make sure that we can support this use case.
For packaged publications in a ZIP or Web publications, some of these fragments also make sense even when they don't reference a specific resource. That's the case of the temporal dimension media fragment for example, which can be very useful on an EPUB as well (an EPUB with Media Overlays must includes a total duration).

As we can see, there's a strong case to support standalone fragments, but we might want to identify which ones.

For packaged publications and Web publications, one could argue that a URL or URL reference to one of its resource could also be considered a fragment.

That's why I like your proposal of supporting references to these resources in fragments as well.

Going back to your additional considerations:

Just the HREF (no fragment): Should chapter3.mp3 (without #...) be valid to denote the start of a resource? This could be useful when the position is at the beginning of a specific chapter, or for comic books / FXL. But then we're back to Locator's href. ;) Perhaps this should be named locations instead of fragments. It should contain only valid URLs, including those with fragments, similar to window.location in JavaScript.

I think so and I would extend this to full URLs in the case on Web Publications, not just a path into a container.

I don't think that this needs to be renamed to locations because as I pointed out above, these are fragments from a publication.

Let's go back to the EPUB with Media Overlays example from the draft, this could be updated to:

{
  "modified": "2026-01-28T00:24:00Z",
  "device": {
    "id": "urn:uuid:019c0047-cc8d-7ec4-a3c3-938ccadc020a",
    "name": "Ebook Reader (Pixel 10 Pro)"
  },
  "progression": 0.0174920,
  "fragments": ["#t=40.274", "chapter1.html#par36&t=12.482"]
}

Compared to the previous example:

  • progression and timestamp for the entire publication remain unchanged
  • we can now clearly indicate that par36 is an id in chapter1.html
  • and we can now provide an additional timestamp for the progression into chapter1.html

Now let's use another example with an Audiobook available only as a RWPM: https://readium.org/webpub-manifest/examples/Flatland/manifest.json

A Progression Document could look something like this for this example:

{
  "modified": "2026-01-28T00:24:00Z",
  "device": {
    "id": "urn:uuid:019c0047-cc8d-7ec4-a3c3-938ccadc089b",
    "name": "Audio Player"
  },
  "progression": 0.027492,
  "fragments": ["#t=1390", "http://www.archive.org/download/flatland_rg_librivox/flatland_2_abbott.mp3#t=21"]
}

Multiple HREFs: If we embed HREFs in fragments, what happens with conflicting entries like ["chapter3.mp3#t=32", "chapter4.mp3#t=15"]?

That's always a risk, even without references to specific resources or URLs.

We'll need processing rules for this, but as a rule of thumb we should go from the most specific one to the least specific one.

It's also important to note, that what you can use may be contextual as well. For example, if I'm listening to an EPUB Media Overlay without displaying any text on screen, it's possible to a reading system might only be able to calculate a temporal dimension media fragment.

Finally, it's also good to think of these fragments as a fallback chain. If I can't resolve the temporal dimension in an EPUB Media Overlay, I might still be capable of resolving an HTML ID. And if I can't, I might be able to resolve the progression.

@HadrienGardeur
Copy link
Member Author

Updates so far:

  • added a JSON Schema
  • renamed fragments to references
  • references can contain a path to a given resource in the publication or a URL, in addition to a media fragment
  • added an optional title
  • added HTTP status codes for interactions

@aaronleopold
Copy link

Hello 👋 Thank you for your work pushing the OPDS progression spec forward! I have a little client application which implements the OPDS v2.0 draft, along with a progression implementation based off of this discussion.

I had a quick question around references, but particularly for page-based media (PDFs, CBZs, etc). One thing the Readium locator spec did well imo was disambiguate both the accessing of pages as well the indexing of them (defined here as being a number greater than 0, i.e. 1-based). If dropping an explicit position, I don't think it would be fully clear to clients how to interpret #page=1 as either 0-based or 1-based, that is unless the spec were to explicitly standardize both the reference ident itself (#page, since that is rather symbolic of the resource and not necessarily an explicitly named resource within the publication) and the value (1).

I hope that makes sense! Thanks again for your work on this, excited to see OPDS evolve further.

@HadrienGardeur
Copy link
Member Author

Hello @aaronleopold and thank you for your feeback!

If you're targeting pre-paginated formats (PDF, CBZ and EPUB FXL) the good news is that they're much easier to handle than the other ones.
With these formats, you can take the current page number and divide it by the total number of pages to get a progression and that's pretty much all you need.

I've included page from the PDF specification for the sake of completeness, but I see that this can lead to confusion. I'll update the draft to remove it.

I'd like to keep references strictly when they refine what you get from progression. That's the case with an id, text, spatial and time fragments.

I have a little client application which implements the OPDS v2.0 draft, along with a progression implementation based off of this #67 (comment).

I'm surprised to learn that at least two clients have implemented what @mickael-menu documented in this issue as a private implementation, but the good news is that it should be fairly straightforward to move to the standardized way of handling progression.

@aaronleopold
Copy link

With these formats, you can take the current page number and divide it by the total number of pages to get a progression and that's pretty much all you need.

While I am slightly worried about potential floating-point issues going that route, I think this makes sense and likely will be fine. Thank you for clarifying!

@HadrienGardeur
Copy link
Member Author

@aaronleopold for CBZ and EPUB you can also provide a path to the resource using references:

Let's take a publication with 100 pages in CBZ and we're currently at page 10:

{
  "modified": "2025-12-25T12:00:00Z",
  "device": {
    "id": "urn:uuid:019c0049-6e8c-745c-adb1-5b03f8ad50c4",
    "name": "Comic Reader"
  },
  "progression": 0.1,
  "references": ["page10.jxl"]
}

This is very much optional but can be useful to further disambiguate.

@chocolatkey
Copy link

I think in order for cross-client use of a floating-point number for progression, a recommendation should be provided on the math to be used for turning a floating progression into a page number - specifically, what rounding you use: floor(progression) or round(progression) or some more complex logic. For example, with a 10001 (extreme example) page comic:

5000 / 10001 = 0.49995000499950004
0.49995000499950004 * 10001 = 5000
0.4999 * 10001 = 4999.4999 # If the decimals were truncated by a system for storage

There are many better examples of unlucky large floating point numbers that people happen to land on for novels, audiobooks, comics, that beg the question of whether it's the next page or the previous page you want to land on.

@HadrienGardeur
Copy link
Member Author

HadrienGardeur commented Feb 4, 2026

I'd like to keep this sort of recommendations outside of the main draft/spec ideally.

While there is clearly a risk with rounding errors on floating point numbers, I think that calculating progression for EPUB reflowable will require even more recommendations (see w3c/epub-specs#2892 (comment)).

Rounding errors won't be as much of an issue with timestamps or even reflowable EPUB, since we don't need to round them to an integer in the same way. An error resulting in a few hundred milliseconds shift wouldn't impact the experience much.

This is very much a draft at this stage, in addition to the use of a path in references for CBZ/EPUB, I'm also open to the idea of re-introducing #page with clarifications if you both think that this is a better option.

I'm reluctant to use position from Readium Locators since this is not something that will be universally used across reading systems, but defining #page as an integer >= 1 is easy enough.

Here's what the PDF spec says:

page=<pageNum>
Identifies a specified (physical) page; the first page in the document has a pageNum value of 1.

@mickael-menu
Copy link
Member

I agree that position would be odd in this spec, as there are no standardized way to compute it for reflowable ebooks.

Reintroducing #page= makes sense for PDFs since it is well-specified. It's not very different from #t= which could also be resolved using the progression. For the other fixed layout formats, an HREF in references should be enough.

@HadrienGardeur
Copy link
Member Author

HadrienGardeur commented Feb 5, 2026

I've re-added the reference to the PDF spec for #page and updated examples accordingly.

I've also used this opportunity to add new examples for pre-paginated/FXL EPUB and CBZ.

The draft is still missing better error handling:

  • using Problem Details for HTTP APIs
  • list of potential errors with URLs that will show up in type

Once I'm done with that, I will remove the draft status from this PR. I've mostly received private feedback for now, but mostly positive and from implementers that would use it for many different formats (comics, audiobooks, EPUB or even sync across various formats).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants