# How LunaTV Handles HLS Streaming with HLS.js and ArtPlayer

> Learn how LunaTV achieves low-latency HLS streaming using HLS.js and ArtPlayer. Discover its dynamic quality probing, ad-blocking, and error recovery features.

- Repository: [MoonTechLab/LunaTV](https://github.com/MoonTechLab/LunaTV)
- Tags: how-to-guide
- Published: 2026-09-08

---

**LunaTV streams HLS (M3U8) content by wiring HLS.js into ArtPlayer through the `customType` configuration, enabling low-latency playback with dynamic quality probing, ad-blocking support, and graceful error recovery.**

The MoonTechLab/LunaTV repository implements a sophisticated HLS streaming architecture that combines HLS.js for robust segment handling and ArtPlayer for UI management. This integration allows LunaTV to deliver smooth `.m3u8` playback in the browser while providing advanced features like real-time quality assessment and customizable ad-blocking. By leveraging ArtPlayer's extensible `customType` system, the application treats HLS streams as first-class citizens with optimized buffering strategies and network resilience.

## Video Resolution and Network Probing

Before playback begins, LunaTV evaluates source quality through the `getVideoResolutionFromM3u8` utility located in [`src/lib/utils.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/utils.ts). This function creates a hidden `HTMLVideoElement` and instantiates an `Hls` object to probe the stream metadata without user-visible playback.

The probe attaches listeners to `FRAG_LOADING` and `FRAG_LOADED` events to calculate download speed and ping time. It extracts the video width from the manifest to infer quality tiers (4K, 1080p, 720p). This data feeds into the source selection algorithm that scores candidates based on a weighted formula: **40% quality, 40% speed, and 20% ping time**.

```typescript
// src/lib/utils.ts
export async function getVideoResolutionFromM3u8(m3u8Url: string) {
  const video = document.createElement('video');
  video.muted = true; 
  video.preload = 'metadata';
  
  const hls = new Hls();
  hls.loadSource(m3u8Url);
  hls.attachMedia(video);
  
  // Listen for FRAG_LOADING / FRAG_LOADED to calculate speed
  // Returns { quality, loadSpeed, pingTime }
}

```

## Configuring ArtPlayer for HLS.js Integration

The core integration occurs in [`src/app/play/page.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/play/page.tsx), where the application instantiates `Artplayer` with a `customType` handler for the `m3u8` MIME type. This handler intercepts `.m3u8` URLs and routes them through HLS.js instead of the native HTML5 video engine.

When ArtPlayer encounters an M3U8 source, it invokes the `customType.m3u8` callback with the video element and URL. The implementation checks for existing HLS instances to prevent memory leaks, configures low-latency parameters, and attaches error recovery logic.

```typescript
// src/app/play/page.tsx
artPlayerRef.current = new Artplayer({
  container: artRef.current,
  url: videoUrl,
  poster: videoCover,
  autoplay: true,
  customType: {
    m3u8: (video: HTMLVideoElement, url: string) => {
      if (!Hls) { console.error('HLS.js 未加载'); return; }
      
      // Clean up existing instance
      if (video.hls) video.hls.destroy();
      
      const hls = new Hls({
        debug: false,
        enableWorker: true,
        lowLatencyMode: true,
        maxBufferLength: 30,
        backBufferLength: 30,
        maxBufferSize: 60 * 1_000_000,
        loader: blockAdEnabledRef.current ? CustomHlsJsLoader : Hls.DefaultConfig.loader,
      });
      
      hls.loadSource(url);
      hls.attachMedia(video);
      video.hls = hls;
      
      // Error recovery logic
      hls.on(Hls.Events.ERROR, (event, data) => {
        if (data.fatal) {
          if (data.type === Hls.ErrorTypes.NETWORK_ERROR) hls.startLoad();
          else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) hls.recoverMediaError();
          else hls.destroy();
        }
      });
    },
  },
});

```

## Ad-Blocking and Custom Loader Selection

LunaTV supports dynamic ad-blocking through a custom HLS.js loader. The `loader` parameter in the HLS.js configuration switches between `CustomHlsJsLoader` and `Hls.DefaultConfig.loader` based on the `blockAdEnabledRef` boolean value.

When users enable ad-blocking via the settings panel, the application destroys the current HLS instance and recreates the player with the alternative loader. This pattern ensures that segment fetching logic—including ad filtering—updates immediately without a full page refresh.

```typescript
// Settings panel configuration for ad-blocking
{
  html: '去广告',
  tooltip: blockAdEnabled ? '已开启' : '已关闭',
  onClick() {
    const newVal = !blockAdEnabled;
    localStorage.setItem('enable_blockad', String(newVal));
    
    // Clean up existing HLS instance
    if (artPlayerRef.current?.video?.hls) {
      artPlayerRef.current.video.hls.destroy();
    }
    artPlayerRef.current?.destroy();
    
    setBlockAdEnabled(newVal);
    return newVal ? '当前开启' : '当前关闭';
  },
}

```

## Error Recovery and Low-Latency Optimization

The HLS.js configuration prioritizes **low-latency streaming** through `lowLatencyMode: true` and worker-enabled segment processing (`enableWorker: true`). Buffer parameters are tuned for stability: `maxBufferLength` and `backBufferLength` set to 30 seconds, with a `maxBufferSize` of 60MB.

Error handling distinguishes between recoverable and fatal errors. For `NETWORK_ERROR` events, the player attempts to restart loading via `hls.startLoad()`. Media-related errors trigger `hls.recoverMediaError()`. Only unrecoverable fatal errors result in full instance destruction, ensuring minimal playback interruption during transient network issues.

## Live Stream Support

The same HLS streaming architecture powers live streams in [`src/app/live/page.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/live/page.tsx). This page mirrors the Play page's implementation, utilizing identical ArtPlayer and HLS.js configurations to handle real-time M3U8 broadcasts. The low-latency settings prove particularly critical here, reducing the delay between broadcast and viewer playback.

## Summary

- **CustomType Integration**: LunaTV registers HLS.js as a custom handler in ArtPlayer's `customType` configuration, routing all `.m3u8` sources through HLS.js instead of native playback.
- **Pre-flight Probing**: The `getVideoResolutionFromM3u8` function in [`src/lib/utils.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/utils.ts) analyzes fragment loading events to determine resolution and network speed before committing to a source.
- **Dynamic Loader Switching**: Ad-blocking functionality toggles between `CustomHlsJsLoader` and the default HLS.js loader without page reloads by destroying and recreating the HLS instance.
- **Resilient Error Handling**: Fatal errors trigger conditional recovery—network errors restart loading, media errors attempt recovery, and only catastrophic failures destroy the instance.
- **Optimized Buffering**: Low-latency mode, web workers, and explicit buffer limits (30s length, 60MB size) ensure smooth playback across varying network conditions.

## Frequently Asked Questions

### How does LunaTV determine the best video quality before playback?

LunaTV uses the `getVideoResolutionFromM3u8` utility in [`src/lib/utils.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/utils.ts) to probe each candidate source. It creates a temporary HLS.js instance, measures fragment load times, and extracts width metadata from the stream. Sources are scored using a weighted algorithm favoring resolution (40%), download speed (40%), and latency (20%), then the highest-scoring source is fed to ArtPlayer.

### What happens when an HLS stream encounters a network error?

The application listens for `Hls.Events.ERROR` and implements tiered recovery. If the error is fatal and classified as a `NETWORK_ERROR`, LunaTV calls `hls.startLoad()` to retry fetching segments. For `MEDIA_ERROR` events, it invokes `hls.recoverMediaError()`. Only fatal errors outside these categories trigger full instance destruction, minimizing playback disruption during brief connectivity issues.

### Can users toggle ad-blocking during active HLS playback?

Yes. LunaTV extends ArtPlayer's settings panel with a "去广告" (block ads) toggle. When clicked, the application stores the preference in `localStorage`, destroys the current HLS instance via `video.hls.destroy()`, recreates the ArtPlayer with the updated `blockAdEnabledRef` value, and switches between `CustomHlsJsLoader` and the default HLS.js loader accordingly.

### Why does LunaTV use ArtPlayer instead of the native HTML5 video element for HLS?

ArtPlayer provides a customizable UI layer and settings management that HLS.js lacks alone. By configuring the `customType` property, LunaTV combines HLS.js's robust M3U8 parsing, adaptive bitrate switching, and low-latency capabilities with ArtPlayer's controls, hotkeys, and extended settings—creating a unified streaming experience without sacrificing HLS.js's advanced buffering and error recovery features.