Library / SDK
hanton/Fisheye avatar
hanton/Fisheye

Fisheye: a Metal-based 360 video player for iOS in pure Swift

iOS 360° panorama video player with Metal rendering support (formerly HTY360Player)

2,141 stars430 forksSwiftMIT

At a glance

What is it?
A Swift package that turns flat panoramic video into a touch-rotatable sphere on iOS, with both a SwiftUI wrapper and a UIKit view, and a renderer that moved from OpenGL ES to Metal in version 2.0.
Who is it for?
Fisheye is a good fit for an iOS app that already holds a flat 360 video file and wants a draggable sphere on screen without pulling in a full 3D engine, and the 2.1 releases suggest the renderer is still being tuned rather than left alone. It is the wrong choice if you need stereo eye separation, a live stream, or a desktop or Android target, because the package names iOS as its only platform and the README documents neither of those.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 7 months ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

From HTY360Player to a Metal renderer

The repository is hanton/Fisheye, and its description still records the older name, HTY360Player. That rename is worth knowing about because the best written explanation of the project is a tutorial for a renderer that no longer exists. The README links to an article on building a 360 video player with OpenGL ES 3.0 and GLKit, then adds a note that the tutorial covers version 1.x while version 2.0 and later use Metal rendering and follow similar principles.

That sentence says a lot about what kind of project this is. It is not a wrapper around someone else's video component, and it is not a scene graph. It is a renderer: something that takes a video frame, maps it onto a sphere, and draws that sphere so the viewer can look around by dragging.

The tree at the repository root is small, which is a good sign for something meant to be added as a dependency. There is `Package.swift`, `LICENSE`, `README.md`, `CHANGELOG.md`, a `Sources/` directory, a `Tests/` directory, an `Example/` directory and a `.github/` directory. Nothing in that list suggests a build step, a binary blob, or a vendored engine.

What sits under Sources/Fisheye

The README documents the library layout file by file, and those files describe the whole design. `Core/FisheyeView.swift` is the high-level MTKView wrapper, which means the view you drop into a UIKit hierarchy is a Metal view rather than a GL view, and `Core/FisheyeConfiguration.swift` holds the settings. `Rendering/MetalRenderer.swift` does the drawing and the YUV conversion, `Geometry/Sphere.swift` and `Geometry/SphereGenerator.swift` hold the sphere model and generate it, `Video/VideoPlayer.swift` handles playback, and `Resources/Shaders.metal` holds the Metal shading language.

Two decisions here are more interesting than they first look. The first is that the sphere is generated in pure Swift rather than loaded from a mesh asset. The README lists pure Swift implementation as a feature, and `SphereGenerator.swift` is where that pays off: there is no resource file to ship, and tessellation becomes a parameter instead of a fixed asset.

The second is that `Video/VideoPlayer.swift` sits on AVFoundation. The README describes that file as AVFoundation video playback, so the decoder is the system one. Whatever AVFoundation on the target device can decode, this library can display, and the input is just a file URL.

Adding the package with Swift Package Manager

Installation is the standard Swift Package Manager flow, and the README gives both the Xcode route and the manifest route. The requirements are stated up front: iOS 15.0 or later, Swift 5.0 or later, and Xcode 14.0 or later. Those numbers are not incidental, because the SwiftUI support that arrived in 2.1.0 needs the modern view lifecycle rather than the older one.

The manifest form is three lines, added to the dependencies array of your own `Package.swift`:

swift
dependencies: [
    .package(url: "https://github.com/user/Fisheye", from: "2.1.2")
]

Note the version floor in the README is 2.1.2, which is also the newest tag on the releases page. Anyone pinning to a lower floor gets the older API surface, including the pre-SwiftUI entry point.

The Xcode route is described as a menu walk rather than a URL you can paste, so there is nothing to copy from it. The one thing worth flagging is that the README shows a placeholder repository URL rather than the canonical address, so you replace it with the hanton/Fisheye one when you add the dependency.

Driving the sphere from SwiftUI

The SwiftUI entry point is `FisheyeSwiftUIView`, and the basic case is short enough to read in one pass. It takes a video URL and fills whatever space it is given:

swift
import SwiftUI
import Fisheye

struct ContentView: View {
    var body: some View {
        if let videoURL = Bundle.main.url(forResource: "video", withExtension: "mp4") {
            FisheyeSwiftUIView(videoURL: videoURL)
                .ignoresSafeArea()
        }
    }
}

The more revealing surface is the modifier chain, because that is where the tunable knobs are exposed in an idiomatic way:

swift
FisheyeSwiftUIView(videoURL: videoURL)
    .fieldOfView(75)
    .touchSensitivity(0.01)
    .sphereQuality(.high)
    .loopPlayback(true)
    .framesPerSecond(60)
    .ignoresSafeArea()

Field of view, touch sensitivity, loop playback and a target frame rate are all per-view here, which means two players on one screen can differ. Sphere quality is a preset rather than a raw slice count, and the README lists four: `.low` at 50 slices, `.medium` at 100, `.high` at 200 as the default, and `.ultra` at 400. That is the main cost dial in the library, since more slices means a finer sphere and more vertices to push through Metal every frame.

Playback is a binding, so the README shows a boolean state driving an `isPlaying` parameter alongside a button that toggles it. For a SwiftUI integration that is about as much wiring as it takes.

The UIKit path and FisheyeConfiguration

For UIKit, `FisheyeView` is created with a frame and added to the view hierarchy like any other view, then pointed at a video and told to play. The README's configuration example is the clearest statement of what the library considers tunable:

swift
let config = FisheyeConfiguration(
    fieldOfView: 90.0,        // Field of view in degrees
    framesPerSecond: 30,      // Target frame rate
    sphereSlices: 100,        // Sphere geometry detail
    touchSensitivity: 0.01,   // Touch rotation sensitivity
    loopPlayback: true        // Loop video playback
)

let fisheyeView = FisheyeView(frame: view.bounds, configuration: config)

Field of view in degrees, target frame rate, sphere slices, touch sensitivity and loop playback. The same five settings the SwiftUI modifiers expose, with the raw slice count available here instead of only through presets.

Playback is a small imperative surface, spelled out in the README as four lines:

swift
fisheyeView.play()     // Start playback
fisheyeView.pause()    // Pause playback
fisheyeView.stop()     // Stop and release resources
fisheyeView.isPlaying  // Check playback state

`stop()` is documented as stopping and releasing resources, which is the one that matters during teardown. `isPlaying` is a readable property rather than something you poll the underlying player for yourself.

What the 2.1 releases changed

Three releases in the 2.1 line describe the project better than any marketing sentence would. Version 2.1.0, published 2026-01-31, added the SwiftUI support with `FisheyeSwiftUIView`, described in the project's own release notes as a native SwiftUI wrapper with support for a video URL binding and playback control, compatible with the SwiftUI lifecycle and state management. That release is what turned the library from a UIKit component into something you can drop into a SwiftUI app.

Version 2.1.1, published 2026-02-16, is a rendering change with a stated reason. The release notes say the Metal render pass was changed to use an explicit `.dontCare` load action instead of the default `.clear`, because 360 degree video covers all pixels, so clearing the full screen every frame was redundant work. That is the kind of change that only gets written when someone has looked at the frame.

Version 2.1.2, published 2026-03-08, added triple buffering for the uniform buffer with semaphore synchronization, described as preventing a CPU and GPU data race. It is also the last push recorded for the repository, so the most recent commit and the most recent tag fall on the same day.

Read together, they point to a renderer being tuned at the pixel and synchronization level rather than a wrapper being expanded. That matters before you commit, because it means the easy wins in this library have already been taken.

Where the README stops

There are gaps, and they are the gaps you would hit first.

Stereo is the notable one. A 360 player is usually wanted for stereoscopic footage, where the left and right eyes get different halves of the frame, and the README never mentions stereo, side-by-side, or top-bottom layouts. `FisheyeConfiguration` has no eye-splitting option, and the sphere quality presets only control tessellation. Planar footage works; stereoscopic footage would need work around the library.

Streaming is the second gap. Every example in the README resolves a file from the app bundle, and the input is a URL rather than an asset identifier, so a remote URL is technically possible, but nothing covers buffering, range requests, or what happens when the network stalls mid-playback.

Platform scope is the third. The requirements name iOS only. There is no macOS or tvOS target listed, and `MTKView` plus AVFoundation would need separate attention on those platforms.

The alternative worth comparing against is not another 360 library but the two paths the project itself illustrates. Writing the projection yourself on top of AVPlayer and an MTKView keeps full control over stereo layout, streaming and overlays, at the cost of owning the geometry, the shaders and the synchronization work that 2.1.1 and 2.1.2 spent time on. A general purpose 3D framework gives you stereo and streaming for free, and you write scene code instead of shader code. Fisheye wins when the video is already flat and the app is already iOS, because it removes the renderer rather than the scene.

On licensing, the package is MIT, and the README credits its sphere generation algorithm to code from the OpenGL ES 3.0 Programming Guide, copyright 2013 Dan Ginsburg and Budirijanto Purnomo, also under the MIT license. Both notices sit in the README's third-party licenses section, which is the kind of attribution a company should read rather than skim.

The repository's example projects are the fastest way to see any of this working: `Example/FisheyeSwiftUIExample` and `Example/FisheyeExample` each ship an Xcode project you open and run, and `Example/Screenshot` holds the demo animation the README uses.

Editorial conclusion

Fisheye is a good fit for an iOS app that already holds a flat 360 video file and wants a draggable sphere on screen without pulling in a full 3D engine, and the 2.1 releases suggest the renderer is still being tuned rather than left alone. It is the wrong choice if you need stereo eye separation, a live stream, or a desktop or Android target, because the package names iOS as its only platform and the README documents neither of those. Open Example/FisheyeSwiftUIExample/FisheyeSwiftUIExample.xcodeproj to see the video path and the modifiers in context, then judge the sphere quality presets against the footage you actually ship.

Frequently asked questions

What minimum iOS version does Fisheye require?

The README states iOS 15.0 or later, Swift 5.0 or later, and Xcode 14.0 or later. The SwiftUI wrapper added in version 2.1.0 is the reason the floor sits as high as it does.

How do I add Fisheye to an Xcode project?

The README describes the Swift Package Manager route: add the package from the File menu in Xcode, or declare it in your own Package.swift as a dependency pinned from version 2.1.2. The README shows a placeholder repository URL that has to be swapped for the hanton/Fisheye address.

Does Fisheye support stereoscopic 360 video?

Nothing in the README documents stereo, side-by-side, or top-bottom layouts. The configuration options cover field of view, frame rate, sphere slices, touch sensitivity and loop playback, and none of them split the frame per eye.

What do the sphere quality presets actually change?

They change how finely the sphere is tessellated. The README lists four presets: low at 50 slices, medium at 100, high at 200 as the default, and ultra at 400. In UIKit you can set the slice count directly through FisheyeConfiguration rather than choosing a preset.

Official sources

  1. hanton/Fisheye on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hanton-fisheye.svg)](https://hysenlabs.com/projects/hanton-fisheye)