PageSourceSearch

https://www.owdcbhqtx.cc/usr/plugins/DPlayer/assets/hevc-source-selector.js?v=20260730-hevc-dual-v1

js owdcbhqtx.cc collected 2026-09-26 05:36:31 UTC 14,423 bytes, 491 lines download raw bytes

1;(function (root, factory) {
2  const api = factory()
3  if (typeof module === "object" && module.exports) {
4    module.exports = api
5  }
6  if (root) {
7    root.HevcSourceSelector = api
8  }
9})(typeof globalThis !== "undefined" ? globalThis : this, function () {
10  "use strict"
11
12  const SOURCE_CONTRACT = "dual-v1"
13  const HEVC_MIME = 'video/mp4; codecs="hvc1.1.6.L120.B0"'
14  const HEVC_MIMES = [HEVC_MIME, 'video/mp4; codecs="hev1.1.6.L120.B0"']
15  const HLS_MIME = "application/vnd.apple.mpegurl"
16  const DEFAULT_PROBE_TIMEOUT_MS = 3000
17
18  const nonEmptyString = value =>
19    typeof value === "string" && value.trim() ? value.trim() : ""
20
21  const normalizeCodec = codec => {
22    const value = nonEmptyString(codec).toLowerCase()
23    return value === "hevc" ||
24      value === "h265" ||
25      value.startsWith("hvc1") ||
26      value.startsWith("hev1")
27      ? "hevc"
28      : "avc"
29  }
30
31  const normalizePayload = input => {
32    let payload = input
33    if (typeof payload === "string") {
34      const raw = payload.trim()
35      if (!raw) {
36        return { primary: "", fallback: "", codec: "avc", legacy: true }
37      }
38      if (raw[0] !== "{") {
39        return { primary: raw, fallback: raw, codec: "avc", legacy: true }
40      }
41      try {
42        payload = JSON.parse(raw)
43      } catch (_) {
44        return { primary: "", fallback: "", codec: "avc", legacy: false }
45      }
46    }
47
48    if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
49      return { primary: "", fallback: "", codec: "avc", legacy: false }
50    }
51    const codec = normalizeCodec(payload.codec)
52    const primary = nonEmptyString(payload.primary)
53    const fallback = nonEmptyString(payload.fallback)
54    if (codec === "hevc") {
55      // HEVC 响应缺少明确的 H.264 兜底时,不得进入播放流程。
56      if (!primary || !fallback) {
57        return { primary: fallback, fallback, codec: "avc", legacy: false }
58      }
59      return { primary, fallback, codec, legacy: false }
60    }
61    const h264 = fallback || primary
62    return { primary: h264, fallback: h264, codec: "avc", legacy: false }
63  }
64
65  const parseVersion = version => {
66    const match = String(version || "").match(/^(\d+)\.(\d+)(?:\.(\d+))?/)
67    return match ? [Number(match[1]), Number(match[2]), Number(match[3] || 0)] : null
68  }
69
70  const isHlsHevcTsCapable = version => {
71    const parsed = parseVersion(version)
72    if (!parsed) return false
73    const [major, minor] = parsed
74    return major > 1 || (major === 1 && minor >= 6)
75  }
76
77  const canPlay = (video, mime) => {
78    try {
79      return !!video?.canPlayType?.(mime)
80    } catch (_) {
81      return false
82    }
83  }
84
85  const normalizeSmooth = value => (value === true ? true : value === false ? false : null)
86
87  const probeDecodingInfo = (mediaCapabilities, type, mime, timeoutMs) => {
88    if (!mediaCapabilities || typeof mediaCapabilities.decodingInfo !== "function") {
89      return Promise.resolve({
90        state: "unknown",
91        smooth: null,
92        reason: "media-capabilities-unavailable"
93      })
94    }
95    let probe
96    try {
97      probe = mediaCapabilities.decodingInfo({
98        type,
99        video: {
100          contentType: mime,
101          width: 1920,
102          height: 1080,
103          bitrate: 5000000,
104          framerate: 30
105        }
106      })
107    } catch (_) {
108      return Promise.resolve({
109        state: "unknown",
110        smooth: null,
111        reason: "media-capabilities-error"
112      })
113    }
114
115    return new Promise(resolve => {
116      let settled = false
117      let timer = null
118      const finish = value => {
119        if (settled) return
120        settled = true
121        if (timer !== null) clearTimeout(timer)
122        resolve(value)
123      }
124
125      timer = setTimeout(
126        () =>
127          finish({
128            state: "unknown",
129            smooth: null,
130            reason: "media-capabilities-timeout"
131          }),
132        timeoutMs
133      )
134      Promise.resolve(probe).then(
135        result => {
136          // supported 才表示能够解码;smooth 只是流畅度预测,不是格式门槛。
137          if (result?.supported === true) {
138            finish({
139              state: "supported",
140              smooth: normalizeSmooth(result.smooth),
141              reason: ""
142            })
143            return
144          }
145          if (result?.supported === false) {
146            finish({
147              state: "unsupported",
148              smooth: normalizeSmooth(result.smooth),
149              reason: "media-capabilities-unsupported"
150            })
151            return
152          }
153          finish({
154            state: "unknown",
155            smooth: null,
156            reason: "media-capabilities-invalid-result"
157          })
158        },
159        () =>
160          finish({
161            state: "unknown",
162            smooth: null,
163            reason: "media-capabilities-error"
164          })
165      )
166    })
167  }
168
169  const detectHevcSupport = async (runtime = {}) => {
170    const globalObject = typeof globalThis !== "undefined" ? globalThis : {}
171    const documentObject = runtime.document || globalObject.document
172    const navigatorObject = runtime.navigator || globalObject.navigator
173    const video = runtime.video || documentObject?.createElement?.("video")
174    const mediaSource =
175      runtime.MediaSource || globalObject.MediaSource || globalObject.WebKitMediaSource
176    const HlsClass = runtime.Hls || globalObject.Hls
177    const mimes = runtime.mime
178      ? [runtime.mime]
179      : Array.isArray(runtime.mimes) && runtime.mimes.length
180        ? runtime.mimes
181        : HEVC_MIMES
182    const defaultMime = mimes[0] || HEVC_MIME
183    const requestedTimeout = Number(runtime.probeTimeoutMs)
184    const probeTimeoutMs =
185      Number.isFinite(requestedTimeout) && requestedTimeout > 0
186        ? requestedTimeout
187        : DEFAULT_PROBE_TIMEOUT_MS
188    const probeDeadline = Date.now() + probeTimeoutMs
189    const remainingProbeMs = () => Math.max(0, probeDeadline - Date.now())
190    let unknownReason = ""
191    let unsupportedSeen = false
192
193    const h264Capability = (state, reason) => ({
194      supported: false,
195      state,
196      engine: "h264",
197      mime: defaultMime,
198      smooth: null,
199      reason
200    })
201    const hevcCapability = (engine, mime, probe) => ({
202      supported: true,
203      state: "supported",
204      engine,
205      mime,
206      smooth: probe.smooth,
207      reason: ""
208    })
209
210    if (!video) {
211      return h264Capability("unknown", "video-element-unavailable")
212    }
213    if (!navigatorObject?.mediaCapabilities) {
214      return h264Capability("unknown", "media-capabilities-unavailable")
215    }
216
217    if (canPlay(video, HLS_MIME)) {
218      for (const mime of mimes) {
219        if (!canPlay(video, mime)) continue
220        const remainingMs = remainingProbeMs()
221        if (remainingMs <= 0) {
222          unknownReason = unknownReason || "media-capabilities-timeout"
223          break
224        }
225        const probe = await probeDecodingInfo(
226          navigatorObject.mediaCapabilities,
227          "file",
228          mime,
229          remainingMs
230        )
231        if (probe.state === "supported") {
232          return hevcCapability("native", mime, probe)
233        }
234        if (probe.state === "unknown") unknownReason = unknownReason || probe.reason
235        else unsupportedSeen = true
236      }
237    }
238
239    let mseCandidate = false
240    try {
241      mseCandidate =
242        !!HlsClass &&
243        isHlsHevcTsCapable(HlsClass.version) &&
244        HlsClass.isSupported?.() === true &&
245        typeof mediaSource?.isTypeSupported === "function"
246    } catch (_) {
247      mseCandidate = false
248    }
249
250    if (mseCandidate) {
251      for (const mime of mimes) {
252        const remainingMs = remainingProbeMs()
253        if (remainingMs <= 0) {
254          unknownReason = unknownReason || "media-capabilities-timeout"
255          break
256        }
257        let mseCodecSupported = false
258        try {
259          mseCodecSupported = mediaSource.isTypeSupported(mime) === true
260        } catch (_) {
261          unknownReason = unknownReason || "media-source-error"
262          continue
263        }
264        if (!mseCodecSupported) {
265          unsupportedSeen = true
266          continue
267        }
268        const probe = await probeDecodingInfo(
269          navigatorObject.mediaCapabilities,
270          "media-source",
271          mime,
272          remainingMs
273        )
274        if (probe.state === "supported") {
275          return hevcCapability("hls", mime, probe)
276        }
277        if (probe.state === "unknown") unknownReason = unknownReason || probe.reason
278        else unsupportedSeen = true
279      }
280    }
281
282    if (unknownReason) return h264Capability("unknown", unknownReason)
283    return h264Capability(
284      "unsupported",
285      unsupportedSeen ? "hevc-codec-unsupported" : "hevc-playback-stack-unavailable"
286    )
287  }
288
289  const choosePlayback = (input, capability = {}) => {
290    const source = normalizePayload(input)
291    if (source.codec === "hevc" && capability.supported === true) {
292      return {
293        primary: source.primary,
294        fallback: source.fallback,
295        codec: "hevc",
296        engine: capability.engine === "native" ? "native" : "hls"
297      }
298    }
299    const h264 =
300      source.codec === "hevc" ? source.fallback : source.primary || source.fallback
301    return { primary: h264, fallback: h264, codec: "avc", engine: "auto" }
302  }
303
304  const shouldFallbackFromHlsError = (source, error) => {
305    if (normalizePayload(source).codec !== "hevc" || !error || typeof error !== "object") {
306      return false
307    }
308    const responseHasCode =
309      !!error.response &&
310      typeof error.response === "object" &&
311      Object.prototype.hasOwnProperty.call(error.response, "code") &&
312      (error.response.code === 0 || !!error.response.code)
313    return error.fatal === true || responseHasCode
314  }
315
316  const shouldFallbackFromMediaError = (source, errorCode) =>
317    normalizePayload(source).codec === "hevc" && [2, 3, 4].includes(Number(errorCode))
318
319  // 所有内部暂停都必须与用户暂停分开记录,自动恢复只能服从最å
319Žä¸€æ¬¡ç”¨æˆ·å‘½ä»¤ã€‚
320  const createPlaybackIntent = media => {
321    let wantsPlayback = false
322    let internalPausePending = false
323    let destroyed = false
324
325    const disableAutoplay = () => {
326      try {
327        if (media) media.autoplay = false
328      } catch (_) {}
329    }
330    const requestPlay = () => {
331      if (destroyed) return false
332      wantsPlayback = true
333      disableAutoplay()
334      return true
335    }
336    const cancelPlayback = () => {
337      if (destroyed) return false
338      wantsPlayback = false
339      internalPausePending = false
340      disableAutoplay()
341      return true
342    }
343    const onPlay = () => {
344      // 前贴片可能已经在同一轮事件中暂停正片,不能提前清除内部暂停标记。
345      if (media?.paused === false) internalPausePending = false
346      disableAutoplay()
347    }
348    const onPause = () => {
349      if (internalPausePending) {
350        internalPausePending = false
351        return
352      }
353      if (media?.paused === false) return
354      cancelPlayback()
355    }
356
357    media?.addEventListener?.("play", onPlay)
358    media?.addEventListener?.("pause", onPause)
359    disableAutoplay()
360
361    const isPlaybackWanted = () => !destroyed && wantsPlayback
362    const playIfWanted = () => {
363      if (!isPlaybackWanted() || typeof media?.play !== "function") return false
364      internalPausePending = false
365      disableAutoplay()
366      try {
367        const result = media.play()
368        result?.catch?.(() => {})
369        return true
370      } catch (_) {
371        return false
372      }
373    }
374    const pauseInternally = () => {
375      if (destroyed || !media || media.paused === true || typeof media.pause !== "function") {
376        disableAutoplay()
377        return false
378      }
379      internalPausePending = true
380      disableAutoplay()
381      try {
382        media.pause()
383        return true
384      } catch (_) {
385        internalPausePending = false
386        return false
387      }
388    }
389    const reloadIfWanted = () => {
390      if (!isPlaybackWanted() || typeof media?.load !== "function") return false
391      pauseInternally()
392      try {
393        media.load()
394      } catch (_) {
395        return false
396      }
397      return playIfWanted()
398    }
399    const destroy = () => {
400      if (destroyed) return
401      destroyed = true
402      wantsPlayback = false
403      internalPausePending = false
404      disableAutoplay()
405      media?.removeEventListener?.("play", onPlay)
406      media?.removeEventListener?.("pause", onPause)
407    }
408
409    return {
410      requestPlay,
411      cancelPlayback,
412      wantsPlayback: isPlaybackWanted,
413      syncAutoplay: () => {
414        const shouldPlay = isPlaybackWanted()
415        disableAutoplay()
416        return shouldPlay
417      },
418      playIfWanted,
419      pauseInternally,
420      reloadIfWanted,
421      destroy
422    }
423  }
424
425  const createFallbackGate = () => {
426    let used = false
427    return {
428      take(source) {
429        const normalized = normalizePayload(source)
430        if (
431          used ||
432          normalized.codec !== "hevc" ||
433          !normalized.fallback ||
434          normalized.fallback === normalized.primary
435        ) {
436          return ""
437        }
438        used = true
439        return normalized.fallback
440      },
441      get used() {
442        return used
443      }
444    }
445  }
446
447  // DPlayer 的 true 首参表示“同步原生媒体事件”,不能被误判为新的用户命令。
448  const wrapPlayerCommands = (player, intent) => {
449    if (!player || !intent) return () => {}
450    const originalPlay = typeof player.play === "function" ? player.play : null
451    const originalPause = typeof player.pause === "function" ? player.pause : null
452
453    if (originalPlay) {
454      player.play = function () {
455        if (arguments[0] !== true) {
456          intent.requestPlay()
457        }
458        return originalPlay.apply(player, arguments)
459      }
460    }
461    if (originalPause) {
462      player.pause = function () {
463        if (arguments[0] !== true) {
464          intent.cancelPlayback()
465        }
466        return originalPause.apply(player, arguments)
467      }
468    }
469
470    return () => {
471      if (originalPlay) player.play = originalPlay
472      if (originalPause) player.pause = originalPause
473    }
474  }
475
476  return {
477    SOURCE_CONTRACT,
478    HEVC_MIME,
479    HEVC_MIMES,
480    normalizePayload,
481    parseVersion,
482    isHlsHevcTsCapable,
483    detectHevcSupport,
484    choosePlayback,
485    shouldFallbackFromHlsError,
486    shouldFallbackFromMediaError,
487    createPlaybackIntent,
488    createFallbackGate,
489    wrapPlayerCommands
490  }
491})

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.