View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vidéos

Ouvrir le menu Fermer le menu
  • Collections
  • Toutes les vidéos
  • À propos

Plus de vidéos

  • À propos
  • Code
  • Create a great ShazamKit experience

    Discover how your app can offer a great audio matching experience with the latest updates to ShazamKit. We'll take you through matching features, updates to audio recognition, and interactions with the Shazam library. Learn tips and best practices for using ShazamKit in your audio apps.

    For more on ShazamKit, check out "Create custom catalogs at scale with ShazamKit" from WWDC22 as well as "Explore ShazamKit" and "Create custom audio experiences with ShazamKit" from WWDC21.

    Ressources

    • ShazamKit
      • Vidéo HD
      • Vidéo SD

    Vidéos connexes

    WWDC22

    • Create custom catalogs at scale with ShazamKit

    WWDC21

    • Explore ShazamKit
  • Rechercher dans cette vidéo…
    • 6:46 - Single match with SHManagedSession

      let managedSession = SHManagedSession()
      
      let result = await managedSession.result()
            
      switch result {
       case .match(let match):
              print("Match found. MediaItemsCount: \(match.mediaItems.count)")
       case .noMatch(_):
              print("No match found")
       case .error(_, _):
              print("An error occurred")
      }
    • 7:16 - Multiple matches with SHManagedSession

      let managedSession = SHManagedSession()
      
      // Continuously match 
      for await result in managedSession.results {
                
       switch result {
        case .match(let match):
              print("Match found. MediaItemsCount: \(match.mediaItems.count)")
        case .noMatch(_):
              print("No match found")
        case .error(_, _):
              print("An error occurred")
       }
      }
    • 7:37 - Stop SHManagedSession

      let managedSession = SHManagedSession()
      
      // Cancel the session
      managedSession.cancel()
    • 8:02 - ShazamKit Matcher with SHManagedSession

      import Foundation
      import ShazamKit
      
      struct MatchResult: Identifiable, Equatable {
          let id = UUID()
          let match: SHMatch?
      }
      
      @MainActor final class Matcher: ObservableObject {
          
          @Published var isMatching = false
          @Published var currentMatchResult: MatchResult?
          
          var currentMediaItem: SHMatchedMediaItem? {
              currentMatchResult?.match?.mediaItems.first
          }
      
          private let session: SHManagedSession
          
          init() {
              
              if let catalog = try? ResourcesProvider.catalog() {
                  session = SHManagedSession(catalog: catalog)
              } else {
                  session = SHManagedSession()
              }
          }
          
          func match() async {
              
              isMatching = true
              
              for await result in session.results {
                  switch result {
                  case .match(let match):
                      Task { @MainActor in
                          self.currentMatchResult = MatchResult(match: match)
                      }
                  case .noMatch(_):
                      print("No match")
                      endSession()
                  case .error(let error, _):
                      print("Error \(error.localizedDescription)")
                      endSession()
                  }
                  stopRecording()
              }
          }
          
          func stopRecording() {
              
              session.cancel()
          }
          
          func endSession() {
              
              // Reset result of any previous match.
              isMatching = false
              currentMatchResult = MatchResult(match: nil)
          }
      }
    • 10:07 - Preparing SHManagedSession

      let managedSession = SHManagedSession()
      
      await managedSession.prepare()
      
      let result = await managedSession.result()
    • 11:39 - SHManagedSession Idle State in SwiftUI

      struct MatchView: View {
          let session: SHManagedSession
          
          var body: some View {
              VStack {
                   Text(session.state == .idle ? "Hear Music?" 
                      : "Matching")
                   if session.state == .matching {
                        ProgressView()
                   } else {
                        Button {
                            // start match
                        } label: {
                            Text("Learn the Dance")
                        }
                   }
              }       
      }
    • 12:25 - SHManagedSession Matching State in SwiftUI

      struct MatchView: View {
          let session: SHManagedSession
          
          var body: some View {
              VStack {
                   Text(session.state == .idle ? "Hear Music?" 
                      : "Matching")
                   if session.state == .matching {
                        ProgressView()
                   } else {
                        Button {
                            // start match
                        } label: {
                            Text("Learn the Dance")
                        }
                   }
              }    
          }
      }
    • 15:23 - Adding with SHLibrary

      func add(mediaItems: [SHMediaItem]) async throws {
      
          try await SHLibrary.default.addItems(mediaItems)
      }
    • 15:34 - Reading with SHLibrary

      struct LibraryView: View {
          var body: some View {
              List(SHLibrary.default.items) { item in
                  MediaItemView(item: item)
              }
          }
      }
    • 16:00 - Reading with SHLibrary in a non-UI context

      // Determine a user’s most popular genre
         
      let currentItems = await SHLibrary.default.items
      
      let genres = currentItems.flatMap { $0.genres }
      
      // count frequency of genres and get the highest
      let mostPopularGenre = highestOccurringGenre(from: genres)
    • 16:25 - SHLibrary Remove

      func remove(mediaItems: [SHMediaItem]) async throws {
          
          try await SHLibrary.default.removeItems(mediaItems)
      }
    • 16:42 - RecentDancesView with SHLibrary read and delete implementation

      import SwiftUI
      import ShazamKit
      
      enum NavigationPath: Hashable {
          case nowPlayingView(videoURL: URL)
          case danceCompletionView
      }
      
      struct RecentDancesView: View {
          private enum ViewConstants {
              static let emptyStateImageName: String = "EmptyStateIcon"
              static let emptyStateTextTitle: String = "No Dances Yet?"
              static let emptyStateTextSubtitle: String = "Find some music to start learning"
              static let deleteSwipeViewOpacity: Double = 0.5
              static let matchingStateTextTopPadding: CGFloat = 24
              static let matchingStateTextBottomPadding: CGFloat = 16
              static let progressViewScaleEffect: CGFloat = 1.1
              static let progressViewBottomPadding: CGFloat = 12.0
              static let learnDanceButtonWidth: CGFloat = 250
              static let curvedTopSideRectangleHeight: CGFloat = 200
              static let listRowBottomInset: CGFloat = 30.0
              static let matchingStateText: String = "Get Ready..."
              static let notMatchingStateText: String = "Hear Music?"
              static let noMatchText: String = "No dance video for audio"
              static let navigationTitleText: String = "Recent Dances"
              static let learnDanceButtonText: String = "Learn the Dance"
              static let retryButtonText: String = "Try Again"
              static let cancelButtonText: String = "Cancel"
          }
          
          // MARK: Properties
          private var isListEmpty: Bool {
              SHLibrary.default.items.isEmpty
          }
          
          @State private var matchingState: String = ViewConstants.notMatchingStateText
          @State private var matchButtonText: String = ViewConstants.learnDanceButtonText
          @State private var canRetryMatchAttempt = false
          @State private var navigationPath: [NavigationPath] = []
          
          // MARK: Environment
          @EnvironmentObject private var matcher: Matcher
          @Environment(\.openURL) var openURL
          var body: some View {
              NavigationStack(path: $navigationPath) {
                  ZStack(alignment: .bottom) {
                      List(SHLibrary.default.items, id: \.self) { mediaItem in
                              RecentDanceRowView(mediaItem: mediaItem)
                                  .onTapGesture(perform: {
                                      guard let appleMusicURL = mediaItem.appleMusicURL else {
                                          return
                                      }
                                      openURL(appleMusicURL)
                                  })
                                  .swipeActions {
                                      Button {
                                          Task { try? await SHLibrary.default.removeItems([mediaItem]) }
                                      } label: {
                                          Image(systemName: "trash")
                                              .symbolRenderingMode(.hierarchical)
                                      }
                                      .tint(.appPrimary.opacity(0.5))
                                  }
                      }
                          .listStyle(.plain)
                          .overlay {
                              if isListEmpty {
                                  ContentUnavailableView {
                                      Label(ViewConstants.emptyStateTextTitle,
                                            image: ImageResource(name: ViewConstants.emptyStateImageName, bundle: Bundle.main))
                                          .font(.title)
                                          .foregroundStyle(Color.white)
                                  } description: {
                                      Text(ViewConstants.emptyStateTextSubtitle)
                                          .foregroundStyle(Color.white)
                                  }
                              }
                          }
                          .safeAreaInset(edge: .bottom, spacing: ViewConstants.listRowBottomInset) {
                              ZStack(alignment: .top) {
                                  CurvedTopSideRectangle()
                                  VStack {
                                      Text(matchingState)
                                          .font(.body)
                                          .foregroundStyle(.white)
                                          .padding(.top, ViewConstants.matchingStateTextTopPadding)
                                          .padding(.bottom, ViewConstants.matchingStateTextBottomPadding)
                                      if matcher.isMatching {
                                          ProgressView()
                                              .progressViewStyle(.circular)
                                              .tint(.appPrimary)
                                              .scaleEffect(x: ViewConstants.progressViewScaleEffect, y: ViewConstants.progressViewScaleEffect)
                                              .padding(.bottom, ViewConstants.progressViewBottomPadding)
                                          Button(ViewConstants.cancelButtonText) {
                                              canRetryMatchAttempt = false
                                              matcher.stopRecording()
                                              matcher.endSession()
                                          }
                                          .foregroundStyle(Color.appPrimary)
                                          .font(.subheadline)
                                          .fontWeight(.semibold)
                                      } else {
                                          Button {
                                              Task { await matcher.match() }
                                              matchingState = ViewConstants.matchingStateText
                                              canRetryMatchAttempt = true
                                          } label: {
                                              Text(matchButtonText)
                                                  .foregroundStyle(.black)
                                                  .font(.title3)
                                                  .fontWeight(.heavy)
                                                  .frame(maxWidth: .infinity)
                                          }
                                          .frame(width: ViewConstants.learnDanceButtonWidth)
                                          .padding()
                                          .background(Color.appPrimary)
                                          .clipShape(Capsule())
                                      }
                                  }
                              }
                              .edgesIgnoringSafeArea(.bottom)
                              .frame(height: ViewConstants.curvedTopSideRectangleHeight)
                          }
                  }
                  .background(Color.appSecondary)
                  .navigationTitle(isListEmpty ? "" : ViewConstants.navigationTitleText)
                  .preferredColorScheme(.dark)
                  .toolbarColorScheme(.dark, for: .navigationBar)
                  .navigationBarTitleDisplayMode(.large)
                  .toolbarBackground(Color.appSecondary, for: .navigationBar)
                  .frame(maxHeight: .infinity)
                  .onChange(of: matcher.currentMatchResult, { _, result in
                      
                      guard navigationPath.isEmpty else {
                          print("Dance video already displayed")
                          return
                      }
                      
                      guard let match = result?.match,
                            let url = ResourcesProvider.videoURL(forFilename: match.mediaItems.first?.videoTitle ?? "") else {
                          
                          matchingState = canRetryMatchAttempt ? ViewConstants.noMatchText : ViewConstants.notMatchingStateText
                          matchButtonText = canRetryMatchAttempt ? ViewConstants.retryButtonText : ViewConstants.learnDanceButtonText
                          return
                      }
                      
                      canRetryMatchAttempt = false
                      
                      // Add the video playing view to the navigation stack.
                      navigationPath.append(.nowPlayingView(videoURL: url))
                  })
                  .navigationDestination(for: NavigationPath.self, destination: { newNavigationPath in
                      switch newNavigationPath {
                      case .nowPlayingView(let videoURL):
                          NowPlayingView(navigationPath: $navigationPath, nowPlayingViewModel: NowPlayingViewModel(player: AVPlayer(url: videoURL)))
                      case .danceCompletionView:
                          DanceCompletionView(navigationPath: $navigationPath)
                      }
                  })
                  .onAppear {
                      if AVAudioSession.sharedInstance().category != .ambient {
                          Task.detached { try? AVAudioSession.sharedInstance().setCategory(.ambient) }
                      }
                      matchingState = ViewConstants.notMatchingStateText
                      matchButtonText = ViewConstants.learnDanceButtonText
                  }
              }
          }
      }
    • 20:23 - Filtering for specific media items

      func match(from televisionShowCatalog: SHCustomCatalog) async -> [SHMatchedMediaItem] {
              
              let managedSession = SHManagedSession(catalog: televisionShowCatalog)
              
              let result = await managedSession.result()
              
              if case .match(let match) = result {
                  
                  // filter for only media items related to a particular episode
                  let filteredMediaItems = match.mediaItems.filter { $0.title == "Episode 2" }
                  return filteredMediaItems
              }
              
              return []
      }

Developer Footer

  • Vidéos
  • WWDC23
  • Create a great ShazamKit experience
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines