Core-Graphics (iPhone) -Can we know whether a CGPath is closed? - core-graphics

Is there any workaround to know where a CGpath (created using an array of co-ordinates) is closed.
I was not able to find any suitable method in the header CGPath.
I wish to track the path traced by user .If its a closed loop, then i wish to extract that part from the context. Something like masking or clipping the context but it should be user tracked clipping.
Thanks!

In fact, a path can consist of multiple subpaths. A new subpath is created when you move the path's current point without connecting the two points. For the path to be closed, all its subpaths must in fact be closed.
extension CGPath {
/// Note that adding a line or curve to the start point of the current
/// subpath does not close it. This can be visualized by stroking such a
/// path with a line cap of `.butt`.
public var isClosed: Bool {
var completedSubpathsWereClosed = true
var currentSubpathIsClosed = true
self.applyWithBlock({ pointer in
let element = pointer.pointee
switch element.type {
case .moveToPoint:
if !currentSubpathIsClosed {
completedSubpathsWereClosed = false
}
currentSubpathIsClosed = true
case .addLineToPoint, .addQuadCurveToPoint, .addCurveToPoint:
currentSubpathIsClosed = false
case .closeSubpath:
currentSubpathIsClosed = true
}
})
return completedSubpathsWereClosed && currentSubpathIsClosed
}
}

Just in case you've been stuck on this for the last 4 1/2 years, here is how to do it in Swift 3. I have borrowed heavily from this answer. I really just added the isClosed() function.
extension CGPath {
func isClosed() -> Bool {
var isClosed = false
forEach { element in
if element.type == .closeSubpath { isClosed = true }
}
return isClosed
}
func forEach( body: #convention(block) (CGPathElement) -> Void) {
typealias Body = #convention(block) (CGPathElement) -> Void
let callback: #convention(c) (UnsafeMutableRawPointer, UnsafePointer<CGPathElement>) -> Void = { (info, element) in
let body = unsafeBitCast(info, to: Body.self)
body(element.pointee)
}
print(MemoryLayout.size(ofValue: body))
let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self))
}
}
And assuming path is a CGPath or CGMutablePath, here is how you use it:
path.isClosed()

Related

Am I able to use the value of a variable to call a variable within my struct?

I have sounds stored for different events within the struct that the user will be able to change, and was hoping i could send the function a string to select a song for a specific event.
my function call would look like this:
func playSound(audio: Audio, soundSelect: String = "startSound"){
if let path = Bundle.main.path(forResource: audio.\(soundSelect), ofType: audio.soundType){
do {
audioPlayer = try! AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
audioPlayer?.play()
}catch{
print("ERROR: Could not find and play the sound file!")
}
and my struct would look something like this:
struct Audio {
var startSound: String = "happyMusic"
var endSound: String = "sadMusic"
var soundType: String = "mp3"
}
as above i tried string interpolation which didn't seem to work I got the error
"Expected member name following '.'
What I expected was for "audio.\(soundSelect)" to be read like "audio.startSound"
You cannot build variable names at runtime because all variable names are evaluated at compile time.
But if you define the type of the parameter as a KeyPath you are able to address different properties in the struct
func playSound(audio: Audio, soundSelect: KeyPath<Audio,String> = \.startSound) {
if let url = Bundle.main.url(forResource: audio[keyPath: soundSelect], withExtension: audio.soundType) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} catch {
print("ERROR: Could not find and play the sound file!", error)
}
}
}
Notes:
If you implement a do - catch block handle (removing the question mark in try?) and print the error rather than just a meaningless string literal.
Bundle provides an URL related API which avoids the extra URL(fileURLWithPath call
A still swiftier syntax is to make the function throw and hand over the error(s) to the caller
func playSound(audio: Audio, soundSelect: KeyPath<Audio,String> = \.startSound) throws {
if let url = Bundle.main.url(forResource: audio[keyPath: soundSelect], withExtension: audio.soundType) {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} else {
throw URLError(.badURL)
}
}

PagerAdapter always getting called two times in ViewPager

I am trying to make a slider between TouchImageView and PlayerView (Exoplayer) but I am unable to catch up with certain issues that are persisting even after several changes. All the suggestions and answers are welcome. Pardon my questioning skills and please let me know if more inputs are needed for your analysis. Kindly also let me know if there is any other alternative to successfully meet my expectations of properly implementing views smoothly in ViewPager.
Problem description:-
Issues related to click on view :-
When the image is clicked, the audio of next video (if any) starts playing in background.
The same issue is with PlayerView. When the video thumbnail is clicked, the audio of clicked video as well as next video plays together.
Issues related to slider :-
When an we slide and reach to an image preceding to a video, the audio starts playing in background. However, after sliding once toward video and sliding again in forward or backward direction from video for once, the audio stops. But this issue persists after viewing more than one images in forward or backward direction of video.
Attempts made by me to solve this issue :-
I tried to use playerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {...}) method in PagerAdapter to handle player states while sliding between views. Unfortunately, I was unable to grasp to use different player states.
I also tried to use viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {...} method in StatusViewer class.
StatusViewer Java class (Setting PagerAdapter class object inViewPager) :-
modelFeedArrayList = (ArrayList<File>) getIntent().getSerializableExtra("modelFeedArrayList");
position = intent.getIntExtra("position", 0);
ImageSlideAdapter imageSlideAdapter = new ImageSlideAdapter(this,modelFeedArrayList,position);
viewPager.setAdapter(imageSlideAdapter);
viewPager.setCurrentItem(position);
viewPager.setOffscreenPageLimit(0);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
File currentFile = modelFeedArrayList.get(position);
String filePath = currentFile.toString();
if (filePath.endsWith(".jpg") || currentPage == position){
currentPage = position;
ImageSlideAdapter.player.pause();
}
else {
currentPage = position;
ImageSlideAdapter.player.play();
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
ImageSliderAdapter (PagerAdapter) (code mentioned below is inside instantiateItem):-
File currentFile = modelFeedArrayList.get(position);
String filePath = currentFile.toString();
if (currentFile.getAbsolutePath().endsWith(".mp4")) {
statusImageView.setVisibility(View.GONE);
playerView.setVisibility(View.VISIBLE);
player = new ExoPlayer.Builder(context).build();
MediaItem mediaItem = MediaItem.fromUri(filePath);
player.addMediaItem(mediaItem);
playerView.setPlayer(player);
player.prepare();
playerView.setBackgroundColor(context.getResources().getColor(android.R.color.black));
playerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
#Override
public void onViewAttachedToWindow(View v) {
Log.d("Filepath", filePath);
Log.d("Position", "" + position);
}
#Override
public void onViewDetachedFromWindow(View v) {
if (filePath.endsWith(".jpg") || currentPage == position || modelFeedArrayList.get(currentPage).getAbsolutePath().endsWith(".jpg")){
currentPage = position;
player.pause();
Objects.requireNonNull(playerView.getPlayer()).pause();
}
else {
player.release();
Objects.requireNonNull(playerView.getPlayer()).release();
}
}
});
} else {
playerView.setVisibility(View.GONE);
statusImageView.setVisibility(View.VISIBLE);
Glide.with(context).load(modelFeedArrayList.get(position)).into(statusImageView);
statusImageView.setBackgroundColor(context.getResources().getColor(android.R.color.black));
}
Objects.requireNonNull(container).addView(itemView);
return itemView;
}
#Override
public void destroyItem(#NonNull #NotNull ViewGroup container, int position, #NonNull #NotNull Object object) {
container.removeView((ConstraintLayout) object);
}
Thank you StackOverflow community for viewing this question. I resolved the above issue by below mentioned modifications :-
Changes in ImageSliderAdapter (PagerAdapter) :-
-> Below mentioned code was added in onViewAttachedToWindow(View v) :-
if (filePath.endsWith(".jpg") || currentPage == position || modelFeedArrayList.get(currentPage).getAbsolutePath().endsWith(".jpg")){
currentPage = position;
player.pause();
Objects.requireNonNull(playerView.getPlayer()).pause();
}
else {
player.pause();
Objects.requireNonNull(playerView.getPlayer()).pause();
if (filePath.endsWith(".mp4")){
player.pause();
Objects.requireNonNull(playerView.getPlayer()).pause();
}
else {
player.play();
Objects.requireNonNull(playerView.getPlayer()).play();
}
}
-> Below mentioned code was added in onViewDetachedFromWindow(View v) :-
if (filePath.endsWith(".mp4")){
player.release();
Objects.requireNonNull(playerView.getPlayer()).release();
}
-> player.play() was added after player.prepare().
Changes in StatusViewer Java class :-
-> The below changes cured the issue of player malfunctioning and player's play state and release state. I used the smoothScroll: false in setCurrentItem.
viewPager.setCurrentItem(position,false);

How to set variable inside of a Coroutine after yielding a webrequest

Okay I will try and explain this to the best of my ability. I have searched and searched all day for a solution to this issue but can't seem to find it. The problem that I am having is that I have a list of scriptable objects that I am basically using for custom properties to create gameobjects off of. One of those properties that I need to get is a Texture2D that I turn into a sprite. Therefor, I am using UnityWebRequest in a Coroutine and am having to yield the response. After I get the response I am trying to set the variable. However even using Lambdas it seems to me that if I yield return the response before the result it will not set the variable. So every time I check the variable after the Coroutine it comes back null. If someone could enlighten me with what I am missing here that would be just great!
Here is the Scriptable Object Class I am using.
[CreateAssetMenu(fileName = "new movie",menuName = "movie")]
public class MovieTemplate : ScriptableObject
{
public string Title;
public string Description;
public string ImgURL;
public string mainURL;
public string secondaryURL;
public Sprite thumbnail;
}
Here is the call to the Coroutine
foreach (var item in nodes)
{
templates.Add(GetMovieData(item));
}
foreach (MovieTemplate movie in templates)
{
StartCoroutine(GetMovieImage(movie.ImgURL, result =>
{
movie.thumbnail = result;
}));
}
Here is the Coroutine itself
IEnumerator GetMovieImage(string url, System.Action<Sprite> result)
{
using (UnityWebRequest web = UnityWebRequestTexture.GetTexture(url))
{
yield return web.SendWebRequest();
var img = DownloadHandlerTexture.GetContent(web);
result(Sprite.Create(img, new Rect(0, 0, img.width, img.height), Vector2.zero));
}
}
From what you desribe it still seems that the texture is somehow disposed as soon as the routine finishes. My guess would be that it happens due to the using block.
I would store the original texture reference
[CreateAssetMenu(fileName = "new movie",menuName = "movie")]
public class MovieTemplate : ScriptableObject
{
public string Title;
public string Description;
public string ImgURL;
public string mainURL;
public string secondaryURL;
public Sprite thumbnail;
public Texture texture;
public void SetSprite(Sprite newSprite, Texture newTexture)
{
if(texture) Destroy(texture);
texture = newTexture;
var tex = (Texture2D) texture;
thumbnail = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), Vector2.zero);
}
}
So you can keep track of the texture itself as well, let it not be collected by the GC but also destroy it when not needed anymore. Usually Texture2D is removed by the GC as soon as there is no reference to it anymore but Texture2D created by UnityWebRequest might behave different.
Than in the webrequest return the texture and don't use using
IEnumerator GetMovieImage(string url, System.Action<Texture> result)
{
UnityWebRequest web = UnityWebRequestTexture.GetTexture(url));
yield return web.SendWebRequest();
if(!web.error)
{
result?.Invoke(DownloadHandlerTexture.GetContent(web));
}
else
{
Debug.LogErrorFormat(this, "Download error: {0} - {1}", web.responseCode, web.error);
}
}
and finally use it like
for (int i = 0; i < templates.Count; i++)
{
int index = i;//If u use i, it will be overriden too so we make a copy of it
StartCoroutine(
GetMovieImage(
templates[index].ImgURL,
result =>
{
templates[index].SetSprite(result);
})
);
}
The problem is with this section of your code :
foreach (MovieTemplate movie in templates)
{
StartCoroutine(GetMovieImage(movie.ImgURL, result =>
{
movie.thumbnail = result;//wrong movie obj
}));
}
Here you will loose refrence to movie object(override by foreach) before the result of callback arrive .
Change it to something like this :
foreach (int i=0;i<templates.Length;i++)
{
int index= i;//If u use i, it will be overriden too so we make a copy of it
StartCoroutine(GetMovieImage(movie.ImgURL, result =>
{
templates[index].thumbnail = result;
}));
}

Appending JsonFile (UrL) in array to display on application

I am trying to add my data from my Json file into the application.I want append the Authors name from the Json file into the empty array.
I have added all of the areas that needed to be added when i run the simulation i get an empty array. I need it display the Authors first name on the simulator.
Does anyone know what i need to do to my code to make it work?
My Code :
var AuthorGlobal = [String]()
class ViewController: UIViewController, UITextFieldDelegate{
#IBOutlet weak var DisplayAuthor: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
DisplayAuthor.text="\(AuthorGlobal)"
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
Alamofire.request(.GET, "http://178.62.83.50/newsletters.json")
.responseJSON { response in
// print(response.request) // original URL request
// print(response.response) // URL response
// print(response.data) // server data
// print(response.result) // result of response serialization
if let _ = response.result.value {
// print("JSON: \(JSON)")
}
let json = JSON(data: response.data!)
if let Author = json["NewsLetter"][0]["Author"].string {
AuthorGlobal.append(Author)
}
if let LastName = json["NewsLetter"][0]["LastName"].string {
print(LastName)
}
if let ArticleTitle = json["NewsLetter"][0]["ArticleTitle"].string {
//Now you got your value
print(ArticleTitle)
}
if let Author = json["NewsLetter"][1]["Author"].string {
//Now you got your value
print(Author)
}
if let LastName = json["NewsLetter"][1]["LastName"].string {
//Now you got your value
print(LastName)
}
if let ArticleTitle = json["NewsLetter"][1]["ArticleTitle"].string {
//Now you got your value
print ("Article Title: " + (ArticleTitle))
}
}
}
I just tried by putting your json in my file system and loading it locally. Below is my code on Swift 2 and it all worked fine. You might want to check the JSON data coming correctly in your service call. Also, try to compare it line by line with my code to see if you missed out something. Its too late for me to call it a day so bear with me to not pointing out the exact root cause in your code!
var AuthorGlobal = [String]()
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var DisplayAuthor: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// DisplayAuthor.text="\(AuthorGlobal)"
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
let filePath = NSBundle.mainBundle().pathForResource("1", ofType: "json")
var fileContents : String = ""
do {
fileContents = try String(contentsOfFile: filePath!, encoding: NSUTF8StringEncoding)
} catch (_) {
}
var json : Dictionary<String,Array<Dictionary<String,String>>> = Dictionary()
do {
json = try NSJSONSerialization.JSONObjectWithData(fileContents.dataUsingEncoding(NSUTF8StringEncoding)!, options:NSJSONReadingOptions.AllowFragments) as! Dictionary<String, Array<Dictionary<String, String>>>
} catch (_) {
}
let array = json["NewsLetter"] as Array?
if let author = array?[0]["Author"] {
AuthorGlobal.append(author)
}
print(AuthorGlobal) // This prints - ["Tom"]
if let LastName = array?[0]["LastName"] {
print(LastName) // This prints - Holton
}
if let ArticleTitle = array?[0]["ArticleTitle"] {
//Now you got your value
print(ArticleTitle) // This prints - XcodeGhost: Apple suffers a major malware infection inside the iOS app store.
}
if let Author = array?[1]["Author"] {
//Now you got your value
print(Author) // This prints - Sam
}
if let LastName = array?[1]["LastName"] {
//Now you got your value
print(LastName) // This prints - Devaney
}
if let ArticleTitle = array?[1]["ArticleTitle"] {
//Now you got your value
print ("Article Title: " + (ArticleTitle)) // This prints - Article Title: Google is 2 Billion Lines of Code
}
}
}

How to play m3u8 encrypted playlists by providing key file separately?

I have a m3u8 playlist file(lets call it prime), which points to another playlist file which in turn has the ts URLs with the key file URL. Using MPMoviePlayer I can currently play the prime m3u8 file.
The segments are encrypted with AES-128 bit encryption and the key file is in the final m3u8 file. Is there a way that I can supply the final m3u8 file and tell the app to use a local key file to decrypt the video, so I don't have to publish the key file publicly.
This is somewhat related to this SO question
I have implemented something similar to this. What we did was:
Encrypt each segment of Live stream segment at runtime with a JWT
Token that has a combination of key value pairs and time stamp for
validation.
Our server knows how to decrypt this key. and when the
decrypted data is valid, the server responds with a .ts file and
hence the playback becomes secure.
Here is the complete working code with steps mentioned:
//Step 1,2:- Initialise player, change the scheme from http to fakehttp and set delete of resource loader. These both steps will trigger the resource loader delegate function so that we can manually handle the loading of segments.
func setupPlayer(stream: String) {
operationQ.cancelAllOperations()
let blckOperation = BlockOperation {
let currentTStamp = Int(Date().timeIntervalSince1970 + 86400)//
let timeStamp = String(currentTStamp)
self.token = JWT.encode(["Expiry": timeStamp],
algorithm: .hs256("qwerty".data(using: .utf8)!))
self.asset = AVURLAsset(url: URL(string: "fake\(stream)")!, options: nil)
let loader = self.asset?.resourceLoader
loader?.setDelegate(self, queue: DispatchQueue.main)
self.asset!.loadValuesAsynchronously(forKeys: ["playable"], completionHandler: {
var error: NSError? = nil
let keyStatus = self.asset!.statusOfValue(forKey: "playable", error: &error)
if keyStatus == AVKeyValueStatus.failed {
print("asset status failed reason \(error)")
return
}
if !self.asset!.isPlayable {
//FIXME: Handle if asset is not playable
return
}
self.playerItem = AVPlayerItem(asset: self.asset!)
self.player = AVPlayer(playerItem: self.playerItem!)
self.playerView.playerLayer.player = self.player
self.playerLayer?.backgroundColor = UIColor.black.cgColor
self.playerLayer?.videoGravity = AVLayerVideoGravityResizeAspect
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd(notification:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: self.playerItem!)
self.addObserver(self, forKeyPath: "player.currentItem.duration", options: [.new, .initial], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.rate", options: [.new, .old], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.currentItem.status", options: [.new, .initial], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.currentItem.loadedTimeRanges", options: [.new], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.currentItem.playbackLikelyToKeepUp", options: [.new], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.currentItem.playbackBufferEmpty", options: [.new], context: &playerViewControllerKVOContext)
})
}
operationQ.addOperation(blckOperation)
}
//Step 2, 3:- implement resource loader delegate functions and replace the fakehttp with http so that we can pass this m3u8 stream to the parser to get the current m3u8 in string format.
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
var url = loadingRequest.request.url?.absoluteString
let contentRequest = loadingRequest.contentInformationRequest
let dataRequest = loadingRequest.dataRequest
//Check if the it is a content request or data request, we have to check for data request and do the m3u8 file manipulation
if (contentRequest != nil) {
contentRequest?.isByteRangeAccessSupported = true
}
if (dataRequest != nil) {
//this is data request so processing the url. change the scheme to http
url = url?.replacingOccurrences(of: "fakehttp", with: "http")
if (url?.contains(".m3u8"))!
{
// do the parsing on background thread to avoid lags
// step 4:
self.parsingHandler(url: url!, loadingRequest: loadingRequest, completion: { (success) in
return true
})
}
else if (url?.contains(".ts"))! {
let redirect = self.generateRedirectURL(sourceURL: url!)
if (redirect != nil) {
//Step 9 and 10:-
loadingRequest.redirect = redirect!
let response = HTTPURLResponse(url: URL(string: url!)!, statusCode: 302, httpVersion: nil, headerFields: nil)
loadingRequest.response = response
loadingRequest.finishLoading()
}
return true
}
return true
}
return true
}
func parsingHandler(url: String, loadingRequest: AVAssetResourceLoadingRequest, completion:((Bool)->Void)?) -> Void {
DispatchQueue.global(qos: .background).async {
var string = ""
var originalURIStrings = [String]()
var updatedURIStrings = [String]()
do {
let model = try M3U8PlaylistModel(url: url)
if model.masterPlaylist == nil {
//Step 5:-
string = model.mainMediaPl.originalText
let array = string.components(separatedBy: CharacterSet.newlines)
if array.count > 0 {
for line in array {
//Step 6:-
if line.contains("EXT-X-KEY:") {
//at this point we have the ext-x-key tag line. now tokenize it with , and then
let furtherComponents = line.components(separatedBy: ",")
for component in furtherComponents {
if component.contains("URI") {
// Step 7:-
//save orignal URI string to replaced later
originalURIStrings.append(component)
//now we have the URI
//get the string in double quotes
var finalString = component.replacingOccurrences(of: "URI=\"", with: "").replacingOccurrences(of: "\"", with: "")
finalString = "\"" + finalString + "&token=" + self.token! + "\""
finalString = "URI=" + finalString
updatedURIStrings.append(finalString)
}
}
}
}
}
if originalURIStrings.count == updatedURIStrings.count {
//Step 8:-
for uriElement in originalURIStrings {
string = string.replacingOccurrences(of: uriElement, with: updatedURIStrings[originalURIStrings.index(of: uriElement)!])
}
//print("String After replacing URIs \n")
//print(string)
}
}
else {
string = model.masterPlaylist.originalText
}
}
catch let error {
print("Exception encountered")
}
loadingRequest.dataRequest?.respond(with: string.data(using: String.Encoding.utf8)!)
loadingRequest.finishLoading()
if completion != nil {
completion!(true)
}
}
}
func generateRedirectURL(sourceURL: String)-> URLRequest? {
let redirect = URLRequest(url: URL(string: sourceURL)!)
return redirect
}
Implement Asset Resource Loader Delegate for custom handling of streams.
Fake the scheme of live stream so that the Resource loader delegate gets called (for normal http/https it doesn't gets called and player tries to handle the stream itself)
Replace the Fake Scheme with Http scheme.
Pass the stream to M3U8 Parser to get the m3u8 file in plain text format.
Parse the plain string to find EXT-X-KEY tags in the current string.
Tokenise the EXT-X-KEY line to get to the "URI" method string.
Append JWT token separately made, with the current URI method in the m3u8.
Replace all instances of URI in the current m3u8 string with the new token appended URI string.
Convert this string to NSData format
Feed it to the player again.
Hope this helps!
Yes -- You can modify the final m3u8 file before passing it to the player. For example, change the KEY lines to refer to http://localhost/key. Then you would want to run a local http server such as cocoahttpserver to deliver the key to the video player.