流式传输,播放列表,下载歌曲

master
panqihua 5 years ago
parent af63582d43
commit bc6d795f8b
  1. 12
      cloudmusic.xcodeproj/project.pbxproj
  2. 29
      cloudmusic/Alert.swift
  3. 89
      cloudmusic/Base.lproj/Main.storyboard
  4. 94
      cloudmusic/ExtensionUIView.swift
  5. 36
      cloudmusic/MusicRequest.swift
  6. 34
      cloudmusic/MyTabBar.swift
  7. 260
      cloudmusic/PlayController.swift
  8. 82
      cloudmusic/PlayListController.swift
  9. 54
      cloudmusic/SearchController.swift
  10. 7
      cloudmusic/cloudmusic.xcdatamodeld/cloudmusic.xcdatamodel/contents

@ -21,7 +21,8 @@
7151CAEE24B00ED4004C4563 /* PlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7151CAED24B00ED4004C4563 /* PlayController.swift */; }; 7151CAEE24B00ED4004C4563 /* PlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7151CAED24B00ED4004C4563 /* PlayController.swift */; };
7151CAF024B01A6B004C4563 /* SearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7151CAEF24B01A6B004C4563 /* SearchController.swift */; }; 7151CAF024B01A6B004C4563 /* SearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7151CAEF24B01A6B004C4563 /* SearchController.swift */; };
7151CAF524B02DDA004C4563 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 7151CAF424B02DDA004C4563 /* SwiftyJSON */; }; 7151CAF524B02DDA004C4563 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 7151CAF424B02DDA004C4563 /* SwiftyJSON */; };
7151CB0124B042BC004C4563 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7151CB0024B042BC004C4563 /* Alert.swift */; }; 7151CB0124B042BC004C4563 /* ExtensionUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7151CB0024B042BC004C4563 /* ExtensionUIView.swift */; };
7151CB0524B07BD2004C4563 /* MyTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7151CB0424B07BD2004C4563 /* MyTabBar.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -60,7 +61,8 @@
7151CAEB24B001EE004C4563 /* MusicRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicRequest.swift; sourceTree = "<group>"; }; 7151CAEB24B001EE004C4563 /* MusicRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicRequest.swift; sourceTree = "<group>"; };
7151CAED24B00ED4004C4563 /* PlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayController.swift; sourceTree = "<group>"; }; 7151CAED24B00ED4004C4563 /* PlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayController.swift; sourceTree = "<group>"; };
7151CAEF24B01A6B004C4563 /* SearchController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchController.swift; sourceTree = "<group>"; }; 7151CAEF24B01A6B004C4563 /* SearchController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchController.swift; sourceTree = "<group>"; };
7151CB0024B042BC004C4563 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; }; 7151CB0024B042BC004C4563 /* ExtensionUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionUIView.swift; sourceTree = "<group>"; };
7151CB0424B07BD2004C4563 /* MyTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyTabBar.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -124,7 +126,8 @@
7151CAEB24B001EE004C4563 /* MusicRequest.swift */, 7151CAEB24B001EE004C4563 /* MusicRequest.swift */,
7151CAED24B00ED4004C4563 /* PlayController.swift */, 7151CAED24B00ED4004C4563 /* PlayController.swift */,
7151CAEF24B01A6B004C4563 /* SearchController.swift */, 7151CAEF24B01A6B004C4563 /* SearchController.swift */,
7151CB0024B042BC004C4563 /* Alert.swift */, 7151CB0024B042BC004C4563 /* ExtensionUIView.swift */,
7151CB0424B07BD2004C4563 /* MyTabBar.swift */,
); );
path = cloudmusic; path = cloudmusic;
sourceTree = "<group>"; sourceTree = "<group>";
@ -286,11 +289,12 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
7151CB0124B042BC004C4563 /* Alert.swift in Sources */, 7151CB0124B042BC004C4563 /* ExtensionUIView.swift in Sources */,
7151CAEE24B00ED4004C4563 /* PlayController.swift in Sources */, 7151CAEE24B00ED4004C4563 /* PlayController.swift in Sources */,
7151CADF24AFE612004C4563 /* PlayListController.swift in Sources */, 7151CADF24AFE612004C4563 /* PlayListController.swift in Sources */,
7151CAB424AFE14D004C4563 /* cloudmusic.xcdatamodeld in Sources */, 7151CAB424AFE14D004C4563 /* cloudmusic.xcdatamodeld in Sources */,
7151CAEC24B001EF004C4563 /* MusicRequest.swift in Sources */, 7151CAEC24B001EF004C4563 /* MusicRequest.swift in Sources */,
7151CB0524B07BD2004C4563 /* MyTabBar.swift in Sources */,
7151CAA824AFE14D004C4563 /* AppDelegate.swift in Sources */, 7151CAA824AFE14D004C4563 /* AppDelegate.swift in Sources */,
7151CAF024B01A6B004C4563 /* SearchController.swift in Sources */, 7151CAF024B01A6B004C4563 /* SearchController.swift in Sources */,
7151CAAA24AFE14D004C4563 /* SceneDelegate.swift in Sources */, 7151CAAA24AFE14D004C4563 /* SceneDelegate.swift in Sources */,

@ -1,29 +0,0 @@
//
// Alert.swift
// cloudmusic
//
// Created by Qihua Pan on 2020/7/3.
// Copyright © 2020 Qihua Pan. All rights reserved.
//
import UIKit
import CoreData
extension UIViewController{
func alert(message:String,title:String="警告信息",preferredStyle:UIAlertController.Style=UIAlertController.Style.alert,style:UIAlertAction.Style=UIAlertAction.Style.destructive){
let alertController = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
alertController.addAction(UIAlertAction(title: "确认", style:style, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
//
func getContext()-> NSManagedObjectContext {
//
let appDelegate = UIApplication.shared.delegate as! AppDelegate
//
//let managedObjectContect = appDelegate.persistentContainer.viewContext
//
return appDelegate.persistentContainer.viewContext
}
}

@ -7,10 +7,10 @@
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Tab Bar Controller--> <!--My Tab Bar-->
<scene sceneID="yl2-sM-qoP"> <scene sceneID="yl2-sM-qoP">
<objects> <objects>
<tabBarController id="49e-Tb-3d3" sceneMemberID="viewController"> <tabBarController id="49e-Tb-3d3" customClass="MyTabBar" customModule="cloudmusic" customModuleProvider="target" sceneMemberID="viewController">
<tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA"> <tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA">
<rect key="frame" x="0.0" y="975" width="768" height="49"/> <rect key="frame" x="0.0" y="975" width="768" height="49"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
@ -39,6 +39,15 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</tableView> </tableView>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nUq-jo-T3O">
<rect key="frame" x="109" y="143" width="196" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="30"/>
<state key="normal" title="清空播放列表"/>
<connections>
<action selector="pressClear:" destination="Nbr-zX-YvN" eventType="touchUpInside" id="ieu-JK-5oD"/>
</connections>
</button>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="Kug-6K-cM5"/> <viewLayoutGuide key="safeArea" id="Kug-6K-cM5"/>
@ -50,7 +59,7 @@
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="kF2-IP-0Wq" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="kF2-IP-0Wq" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="-772" y="327"/> <point key="canvasLocation" x="-772.46376811594212" y="326.78571428571428"/>
</scene> </scene>
<!--播放--> <!--播放-->
<scene sceneID="NDX-nT-lkJ"> <scene sceneID="NDX-nT-lkJ">
@ -64,44 +73,63 @@
<rect key="frame" x="87" y="126" width="240" height="128"/> <rect key="frame" x="87" y="126" width="240" height="128"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="IIQ-1o-bMh"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Ryn-W9-Eef">
<rect key="frame" x="168" y="281" width="71" height="36"/> <rect key="frame" x="20" y="447" width="374" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="30"/> <fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Ryn-W9-Eef"> <progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nSL-9z-TOF">
<rect key="frame" x="168" y="325" width="71" height="36"/> <rect key="frame" x="87" y="325" width="240" height="2"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="30"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" fixedFrame="YES" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="nSL-9z-TOF">
<rect key="frame" x="87" y="447" width="256" height="2"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</progressView> </progressView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="previous" id="cje-ai-bPk"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="E1J-pc-P7G">
<rect key="frame" x="87" y="600" width="50" height="50"/> <rect key="frame" x="182" y="600" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> <state key="normal" title="Button" image="play"/>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="next" id="JAt-Tx-hWz"> <connections>
<rect key="frame" x="293" y="600" width="50" height="50"/> <action selector="pressPlay:" destination="IEQ-xx-jyn" eventType="touchUpInside" id="9fL-hG-EIF"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="JjE-1k-Wp4">
<rect key="frame" x="277" y="600" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> <state key="normal" title="Button" image="next"/>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="play" id="Koi-FD-jGR"> <connections>
<rect key="frame" x="189" y="600" width="50" height="50"/> <action selector="pressNext:" destination="IEQ-xx-jyn" eventType="touchUpInside" id="Vah-r1-eyg"/>
<action selector="pressPlay:" destination="IEQ-xx-jyn" eventType="touchUpInside" id="sZG-o2-L7z"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="HRB-c3-2n3">
<rect key="frame" x="87" y="600" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> <state key="normal" title="Button" image="previous"/>
<connections>
<action selector="pressPlay:" destination="IEQ-xx-jyn" eventType="touchUpInside" id="VYy-jC-lPg"/>
<action selector="pressPrev:" destination="IEQ-xx-jyn" eventType="touchUpInside" id="igG-fW-Lx1"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Q6S-pp-bF3">
<rect key="frame" x="20" y="508" width="374" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="ybG-XY-4xE"/> <viewLayoutGuide key="safeArea" id="ybG-XY-4xE"/>
</view> </view>
<tabBarItem key="tabBarItem" title="播放" id="LTq-mh-JP7"/> <tabBarItem key="tabBarItem" title="播放" image="first" id="LTq-mh-JP7"/>
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/> <simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
<connections> <connections>
<outlet property="progress" destination="nSL-9z-TOF" id="J9o-AG-9GH"/> <outlet property="musicName" destination="Ryn-W9-Eef" id="XOe-9Q-NV9"/>
<outlet property="nextButton" destination="JjE-1k-Wp4" id="JQC-m5-yxd"/>
<outlet property="playButton" destination="E1J-pc-P7G" id="4dC-CP-c9Y"/>
<outlet property="prevButton" destination="HRB-c3-2n3" id="MgH-hx-Vyb"/>
<outlet property="progress" destination="nSL-9z-TOF" id="UvS-8r-B8l"/>
<outlet property="timeLabel" destination="Q6S-pp-bF3" id="dTA-W5-w6b"/>
</connections> </connections>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="1bd-h6-NBe" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="1bd-h6-NBe" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
@ -116,13 +144,13 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="请输入关键词" textAlignment="natural" minimumFontSize="17" id="sEc-Yc-Wes"> <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="请输入关键词" textAlignment="natural" minimumFontSize="17" id="sEc-Yc-Wes">
<rect key="frame" x="57" y="227" width="201" height="39.5"/> <rect key="frame" x="57" y="227" width="201" height="39.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="25"/> <fontDescription key="fontDescription" type="system" pointSize="25"/>
<textInputTraits key="textInputTraits"/> <textInputTraits key="textInputTraits"/>
</textField> </textField>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="tUw-aS-iW0"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="tUw-aS-iW0">
<rect key="frame" x="293" y="224" width="51" height="42"/> <rect key="frame" x="293" y="224" width="51" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="25"/> <fontDescription key="fontDescription" type="system" pointSize="25"/>
@ -140,9 +168,10 @@
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="X1b-Gk-lAx"/> <viewLayoutGuide key="safeArea" id="X1b-Gk-lAx"/>
</view> </view>
<tabBarItem key="tabBarItem" title="搜索" id="6FF-jA-ogW"/> <tabBarItem key="tabBarItem" title="搜索" image="second" id="6FF-jA-ogW"/>
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/> <simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
<connections> <connections>
<outlet property="item" destination="6FF-jA-ogW" id="URK-Kf-G68"/>
<outlet property="keyword" destination="sEc-Yc-Wes" id="TTL-EO-sxq"/> <outlet property="keyword" destination="sEc-Yc-Wes" id="TTL-EO-sxq"/>
<outlet property="musicTable" destination="6rp-N1-MZ4" id="4g5-vA-jpy"/> <outlet property="musicTable" destination="6rp-N1-MZ4" id="4g5-vA-jpy"/>
</connections> </connections>
@ -153,9 +182,11 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="first" width="30" height="30"/>
<image name="list.dash" catalog="system" width="128" height="85"/> <image name="list.dash" catalog="system" width="128" height="85"/>
<image name="next" width="200" height="200"/> <image name="next" width="200" height="200"/>
<image name="play" width="200" height="200"/> <image name="play" width="200" height="200"/>
<image name="previous" width="200" height="200"/> <image name="previous" width="200" height="200"/>
<image name="second" width="30" height="30"/>
</resources> </resources>
</document> </document>

@ -0,0 +1,94 @@
//
// Alert.swift
// cloudmusic
//
// Created by Qihua Pan on 2020/7/3.
// Copyright © 2020 Qihua Pan. All rights reserved.
//
import UIKit
import CoreData
extension UIViewController{
func alertModal(message:String,title:String="警告信息",preferredStyle:UIAlertController.Style=UIAlertController.Style.alert,style:UIAlertAction.Style=UIAlertAction.Style.destructive){
let alertController = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
alertController.addAction(UIAlertAction(title: "确认", style:style, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
//
func getContext()-> NSManagedObjectContext {
//
let appDelegate = UIApplication.shared.delegate as! AppDelegate
//
//let managedObjectContect = appDelegate.persistentContainer.viewContext
//
return appDelegate.persistentContainer.viewContext
}
func getMusic(id: Int32,name: String) -> Music {
let fetchRequest: NSFetchRequest = Music.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %@", NSNumber(value: id))
do {
let result: [Music] = try self.getContext().fetch(fetchRequest)
debugPrint("count=\(result.count)")
if result.count > 0{
return result[0]
}else{
let music = NSEntityDescription.insertNewObject(forEntityName: "Music", into: self.getContext()) as! Music
music.id=id
music.name=name
music.status=false
return music
}
} catch {
fatalError();
}
}
func clearAll(){
let fetchRequest: NSFetchRequest = Music.fetchRequest()
let context=self.getContext()
do {
let result = try context.fetch(fetchRequest)
for item in result{
context.delete(item)
}
do {
try self.getContext().save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
} catch {
fatalError();
}
}
func getPlayList()->[Music]{
let fetchRequest: NSFetchRequest = Music.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "status == true")
do {
let result: [Music] = try self.getContext().fetch(fetchRequest)
return result
} catch {
fatalError();
}
}
//
func saveContext() {
do {
try self.getContext().save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}

@ -15,14 +15,12 @@ class MusicRequest: NSObject {
// //
private static let request_host = "192.168.50.73:5000" private static let request_host = "192.168.50.73:5000"
// private static func request(url:String,callback:@escaping (JSON?) -> Void){
static func search(keyword:String,callback:@escaping (JSON?) -> Void ){
let url="http://\(String(describing: request_host))/search?keywords=\(keyword)"
debugPrint("搜索请求接口地址:\(url)") debugPrint("搜索请求接口地址:\(url)")
AF.request(url).responseJSON { response in AF.request(url).responseJSON { response in
debugPrint(response) debugPrint(response)
if let json = try? JSON(data: response.data!){ if let data=response.data,let json = try? JSON(data: data){
callback(json) callback(json)
}else{ }else{
callback(nil) callback(nil)
@ -30,4 +28,34 @@ class MusicRequest: NSObject {
} }
} }
//
static func search(keyword:String,callback:@escaping (JSON?) -> Void ){
let url="http://\(String(describing: request_host))/search?keywords=\(keyword)"
request(url: url, callback: callback)
}
//url
static func searchUrl(id:Int32,callback:@escaping (JSON?) -> Void ){
let url="http://\(String(describing: request_host))/song/url?id=\(id)"
request(url: url, callback: callback)
}
//
static func saveMusic(url:String,music:Music,callback:@escaping (Progress,URL) -> Void){
let documentsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("\(music.id).mp3")
let destination: DownloadRequest.Destination = { _, _ in
return (fileURL, [])
}
AF.download(url, to: destination)
.downloadProgress { progress in
callback(progress,fileURL)
}
.responseData { response in
if let data=response.value{
debugPrint("文件体积:\(data.count/(1024*1024))MB")
}
}
}
} }

@ -0,0 +1,34 @@
//
// MyTabBar.swift
// cloudmusic
//
// Created by Qihua Pan on 2020/7/4.
// Copyright © 2020 Qihua Pan. All rights reserved.
//
import UIKit
class MyTabBar: UITabBarController {
static var myTabBar:MyTabBar?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

@ -7,12 +7,66 @@
// //
import UIKit import UIKit
import AVFoundation
import MediaPlayer
class PlayController: UIViewController { class PlayController: UIViewController {
@IBOutlet weak var musicName: UILabel!
@IBOutlet weak var progress: UIProgressView! @IBOutlet weak var progress: UIProgressView!
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var playButton: UIButton!
@IBOutlet weak var prevButton: UIButton!
@IBOutlet weak var nextButton: UIButton!
var activeIndex:Int = -1
var playList:[Music] = []
private var avplayer: AVPlayer!//
private var playerItem: AVPlayerItem!//
var timeObserver: Any! //
@IBAction func pressPlay(_ sender: UIButton) {
if self.avplayer.rate>0{
self.avplayer.pause()
self.playButton.setImage(UIImage(named: "play"), for: .normal)
}else{
self.avplayer.play()
self.playButton.setImage(UIImage(named: "pause"), for: .normal)
}
}
@IBAction func pressNext(_ sender: UIButton) {
if self.activeIndex + 1 == self.playList.count{
self.activeIndex = 0
}else{
self.activeIndex += 1
}
self.play()
}
@IBAction func pressPrev(_ sender: UIButton) {
if self.activeIndex - 1 == -1{
self.activeIndex = self.playList.count-1
}else{
self.activeIndex -= 1
}
self.play()
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -20,8 +74,214 @@ class PlayController: UIViewController {
progress.transform = CGAffineTransform(scaleX: 1, y: 3.0) progress.transform = CGAffineTransform(scaleX: 1, y: 3.0)
self.playList=self.getPlayList()
if(self.playList.count>0){
self.activeIndex=0
self.play()
}
}
override func viewDidAppear(_ animated: Bool) {
self.playList=self.getPlayList()
if self.playList.count==0{
self.musicName.text=""
self.alertModal(message: "当前播放列表为空!")
}
if self.playList.count<2{
self.prevButton.isHidden=true
self.nextButton.isHidden=true
}else{
self.prevButton.isHidden=false
self.nextButton.isHidden=false
}
}
func addPlayerItemObserver(){
// AVPlayerItemstatus
playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
// AVPlayerItemloadedTimeRanges
playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
// NotificationCenter.default.addObserver(self,
// selector: #selector(playerItemDidReachEnd(notification:)),
// name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
}
/// KVO
///
/// - parameter keyPath:
/// - parameter object:
/// - parameter change:
/// - parameter context:
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
guard let object = object as? AVPlayerItem else { return }
guard let keyPath = keyPath else { return }
if keyPath == "status" {
if object.status == .readyToPlay { //
self.avplayer.play()
self.playButton.setImage(UIImage(named: "pause"), for: .normal)
} else if object.status == .failed || object.status == .unknown {
self.alertModal(message: "解析音频失败")
}
} else if keyPath == "loadedTimeRanges" {
let loadedTime = availableDurationWithplayerItem()
let f = CMTimeGetSeconds(object.duration)/loadedTime
self.progress.progress=Float(f)
if f==1.0{
self.progress.isHidden=true
}
print("当前加载进度\(CMTimeGetSeconds(object.duration)/loadedTime)")
}
}
func addProgressObserver(){
// .
timeObserver = self.avplayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: Int64(1.0),
timescale: Int32(1.0)),
queue: DispatchQueue.main) { [weak self] (time: CMTime) in
self?.updateProgress(time)
}
}
func updateProgress(_ time: CMTime) {
// CMTimeGetSecondsCMTimeCMTimeNaN
guard let playerItem = playerItem else {
return
}
let currentTime = CMTimeGetSeconds(time)
let totalTime = CMTimeGetSeconds(playerItem.duration)
//
self.timeLabel.text = "\(self.formatPlayTime(seconds: CMTimeGetSeconds(time)))/\(self.formatPlayTime(seconds: totalTime))"
self.progress.setProgress(Float(currentTime/totalTime), animated: true)
print("当前已经播放\(self.formatPlayTime(seconds: CMTimeGetSeconds(time)))")
}
func availableDurationWithplayerItem() -> TimeInterval {
guard let loadedTimeRanges = self.avplayer.currentItem?.loadedTimeRanges,
let first = loadedTimeRanges.first else {
fatalError()
}
//
let timeRange = first.timeRangeValue
let startSeconds = CMTimeGetSeconds(timeRange.start) //
let durationSecound = CMTimeGetSeconds(timeRange.duration)//
let result = startSeconds + durationSecound//
return result
}
//
func formatPlayTime(seconds: Double) -> String {
if !seconds.isNaN && !seconds.isInfinite{
let min = Int(seconds / 60)
let sec = Int(seconds.truncatingRemainder(dividingBy: 60))
return String(format: "%02d:%02d", min, sec)
}else{
return ""
} }
}
func saveLocal(url: String,music: Music){
MusicRequest.saveMusic(url: url,music: music, callback: {(progress,url) in
debugPrint("Download Progress: \(progress.fractionCompleted)")
if progress.isFinished,let name=music.name {
debugPrint("歌曲\(name)下载完毕, 文件绝对路径=\(url.absoluteString)")
// if FileManager.default.fileExists(atPath: url.absoluteString){
// let filepath=url.absoluteString
// music.filepath = filepath
// self.saveContext()
// }
let filepath=url.absoluteString
music.filepath = filepath
self.saveContext()
}
})
}
func play(){
self.play(music: self.playList[self.activeIndex])
}
func play1(){
// if let url=URL(string:url){
// let asset = AVURLAsset(url: url)
// self.playerItem = AVPlayerItem(asset: asset)
// self.avplayer = AVPlayer(playerItem: self.playerItem)
// // ,kvo
// self.addPlayerItemObserver()
// self.addProgressObserver()
// }else if let name=self.playList[self.activeIndex].name{
// self.alertModal(message:"🎵\(name)")
// }
}
func play(url:String,name:String){
if let url=URL(string:url){
let asset = AVURLAsset(url: url)
self.playerItem = AVPlayerItem(asset: asset)
self.avplayer = AVPlayer(playerItem: self.playerItem)
// ,kvo
self.addPlayerItemObserver()
self.addProgressObserver()
}else {
self.alertModal(message:"无法解析🎵\(name)的播放地址")
}
}
func play(index:Int){
self.activeIndex=index
self.play()
}
func play(music:Music){
self.progress.isHidden=false
if let name=music.name{
self.musicName.text=name
if let filepath=music.filepath{
debugPrint("\(name)本地播放地址\(filepath)")
self.play(url: filepath,name:name)
}else if let url=music.url{
debugPrint("\(name)流式播放地址\(url)")
self.play(url: url,name:name)
self.saveLocal(url: url, music: music)
}else{
debugPrint("查找\(name)流式播放地址")
MusicRequest.searchUrl(id: music.id, callback: {(json) in
debugPrint("json:\(String(describing: json))")
if let res=json{
if res["code"]==200{
if res["data"].array != nil && res["data"].array!.count>0 && res["data"][0]["url"].stringValue.count>0{
let url = res["data"][0]["url"].stringValue
debugPrint("音乐🎵播放地址:\(url)")
music.url=url
self.saveContext()
self.saveLocal(url: url, music: music)
self.play(url: url,name:name)
}else{
self.alertModal(message:"无法解析🎵\(String(describing: name))的播放地址")
}
}else{
self.alertModal(message:"无法查找🎵\(String(describing: name))的播放地址")
}
}
})
}
}
}
/* /*
// MARK: - Navigation // MARK: - Navigation

@ -7,10 +7,19 @@
// //
import UIKit import UIKit
import CoreData
class PlayListController: UIViewController { class PlayListController: UIViewController {
@IBOutlet weak var table: UITableView! @IBOutlet weak var table: UITableView!
@IBAction func pressClear(_ sender: UIButton) {
self.clearAll()
self.reload()
}
var musicList:[Music] = []
// : Swiftmark: MARK:- // : Swiftmark: MARK:-
// MARK:- // MARK:-
let cellID = "cell" let cellID = "cell"
@ -26,6 +35,32 @@ class PlayListController: UIViewController {
table.register(UITableViewCell.self, forCellReuseIdentifier: cellID) table.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
} }
override func viewDidAppear(_ animated: Bool) {
self.reload()
if let tabbar = self.tabBarController,let controllers=tabbar.viewControllers,controllers[0] is PlayController{
let play=controllers[0] as! PlayController
let i=IndexPath(row: play.activeIndex, section: 0)
self.table.selectRow(at: i, animated: true, scrollPosition: .none)
}
}
func reload(){
self.musicList = self.getPlayList()
self.table.reloadData()
}
func addMusic(music:Music){
self.saveContext()
self.musicList.append(music)
}
func clearList(){
self.musicList=[]
}
/* /*
// MARK: - Navigation // MARK: - Navigation
@ -46,38 +81,29 @@ extension PlayListController: UITableViewDataSource,UITableViewDelegate{
// MARK:- UITableViewDataSource // MARK:- UITableViewDataSource
// UITableViewDataSourceoption, // UITableViewDataSourceoption,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20 return self.musicList.count
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
/* if let cell = tableView.dequeueReusableCell(withIdentifier: cellID){
// ---------------------------------------------------------------- cell.textLabel?.text = "歌曲名:\(String(describing: musicList[indexPath.row].name!))"
// 使cell return cell
let cellID = "cell" }else{
return UITableViewCell(style:UITableViewCell.CellStyle.default, reuseIdentifier: cellID)
// 1.cell,cell }
var cell = tableView.dequeueReusableCellWithIdentifier(cellID)
// 2.cellnil
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: cellID)
} }
// 3.cell func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
cell?.textLabel?.text = "测试数据\(indexPath.row)" debugPrint("点击了\(self.musicList[indexPath.row].name!)")
if let tabbar = self.tabBarController{
return cell! if let controllers=tabbar.viewControllers{
*/ if controllers[0] is PlayController{
let controller=controllers[0] as! PlayController
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) controller.play(index: indexPath.row)
}
cell?.textLabel?.text = "测试数据\(indexPath.row)" }
}
return cell! } }
// MARK:- UITableViewDelegate
private func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("点击了\(indexPath.row)")
}
} }

@ -13,7 +13,7 @@ class SearchController: UIViewController {
// : Swiftmark: MARK:- // : Swiftmark: MARK:-
// MARK:- // MARK:-
let cellID = "cell" let cellID = "CellIdentifier"
// //
@IBOutlet weak var keyword: UITextField! @IBOutlet weak var keyword: UITextField!
@ -24,10 +24,11 @@ class SearchController: UIViewController {
var musicList:[cloudmusic.Music]=[] var musicList:[cloudmusic.Music]=[]
@IBOutlet weak var item: UITabBarItem!
// //
@IBAction func search(_ sender: UIButton) { @IBAction func search(_ sender: UIButton) {
self.textFieldShouldReturn(keyword) self.keyword.resignFirstResponder()
if let k=keyword{ if let k=keyword{
if k.text!.count>0{ if k.text!.count>0{
MusicRequest.search(keyword: k.text!,callback: {(json) in MusicRequest.search(keyword: k.text!,callback: {(json) in
@ -36,25 +37,24 @@ class SearchController: UIViewController {
if res["code"]==200{ if res["code"]==200{
self.musicList=[] self.musicList=[]
for (_,subJson):(String, JSON) in res["result"]["songs"] { for (_,subJson):(String, JSON) in res["result"]["songs"] {
// debugPrint("index=\(index),subJson=\(subJson)")
let context=self.getContext() let music=self.getMusic(id: subJson["id"].int32Value, name: subJson["name"].stringValue)
let music = NSEntityDescription.insertNewObject(forEntityName: "Music", into: context) as! Music
music.id=subJson["id"].int64Value
music.name=subJson["name"].stringValue
debugPrint("id=\(music.id),name=\(String(describing: music.name))") debugPrint("id=\(music.id),name=\(String(describing: music.name))")
self.musicList.append(music) self.musicList.append(music)
} }
self.musicTable.reloadData() self.musicTable.reloadData()
self.musicTable.isHidden=false self.musicTable.isHidden=false
}else{ }else{
self.alert(message: "请求失败,请稍后再试") self.alertModal(message: "请求失败,请稍后再试")
} }
}else{ }else{
self.alert(message: "请求失败,请稍后再试") self.alertModal(message: "请求失败,请稍后再试")
} }
}) })
}else{ }else{
self.alert(message: "关键词不能为空") self.alertModal(message: "关键词不能为空")
} }
} }
} }
@ -66,6 +66,7 @@ class SearchController: UIViewController {
// //
self.musicTable.dataSource = self self.musicTable.dataSource = self
self.musicTable.delegate = self self.musicTable.delegate = self
self.musicTable.isHidden=true self.musicTable.isHidden=true
// cell // cell
@ -74,7 +75,7 @@ class SearchController: UIViewController {
self.keyword.delegate=self self.keyword.delegate=self
self.keyword.returnKeyType=UIReturnKeyType.done self.keyword.returnKeyType=UIReturnKeyType.done
print(self)
} }
@ -96,15 +97,40 @@ extension SearchController: UITableViewDataSource,UITableViewDelegate{
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) if let cell = tableView.dequeueReusableCell(withIdentifier: cellID){
cell.textLabel?.text = "歌曲名:\(String(describing: musicList[indexPath.row].name!))"
return cell
}else{
return UITableViewCell(style:UITableViewCell.CellStyle.default, reuseIdentifier: cellID)
}
}
cell?.textLabel?.text = "歌曲名:\(String(describing: musicList[indexPath.row].name!))"
return cell! func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
if let tabbar = self.tabBarController{
tabbar.selectedIndex=0
if let controllers=tabbar.viewControllers{
let music=self.musicList[indexPath.row]
if controllers[0] is PlayController{
let controller=controllers[0] as! PlayController
controller.play(music: music)
}
if controllers[2] is PlayListController{
let controller=controllers[2] as! PlayListController
if !music.status{
music.status=true
controller.addMusic(music: music)
}
} }
}
}
}
} }
extension SearchController:UITextFieldDelegate{ extension SearchController:UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool { func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.keyword.resignFirstResponder() self.keyword.resignFirstResponder()

@ -1,10 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Music" representedClassName="Music" syncable="YES" codeGenerationType="class"> <entity name="Music" representedClassName="Music" syncable="YES" codeGenerationType="class">
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/> <attribute name="filepath" optional="YES" attributeType="String"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/> <attribute name="name" optional="YES" attributeType="String"/>
<attribute name="status" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="url" optional="YES" attributeType="String"/>
</entity> </entity>
<elements> <elements>
<element name="Music" positionX="-45" positionY="0" width="128" height="73"/> <element name="Music" positionX="-45" positionY="0" width="128" height="118"/>
</elements> </elements>
</model> </model>
Loading…
Cancel
Save